Files
2026-06-11 12:43:18 +08:00

656 lines
23 KiB
Go

package mcp
import (
"context"
"github.com/google/uuid"
mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/yan1h/agent-coding-workflow/internal/chat"
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
"github.com/yan1h/agent-coding-workflow/internal/project"
)
// registerChatTools 注册 chat 相关 MCP 工具:会话 CRUD、发消息、消息查询、
// prompt 模板 CRUD、模型列表。
//
// 范围边界(spec 决策):LLM endpoint/model 管理、用量统计、附件上传、
// 流式接收(SSE)与 cancel/retry(强依赖流式上下文)不经 MCP 暴露。
// send_message 的 assistant 回复异步生成,调用方用 list_recent_messages 轮询。
func registerChatTools(srv *mcpsdk.Server, deps ServerDeps) {
registerListRecentMessages(srv, deps)
registerListConversations(srv, deps)
registerGetConversation(srv, deps)
registerCreateConversation(srv, deps)
registerUpdateConversationTitle(srv, deps)
registerDeleteConversation(srv, deps)
registerSendMessage(srv, deps)
registerListPromptTemplates(srv, deps)
registerCreatePromptTemplate(srv, deps)
registerUpdatePromptTemplate(srv, deps)
registerDeletePromptTemplate(srv, deps)
registerListModels(srv, deps)
}
// ===== 共用 DTO / helper =====
type conversationDetail struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
ModelID string `json:"model_id"`
ProjectID string `json:"project_id,omitempty"`
WorkspaceID string `json:"workspace_id,omitempty"`
IssueID string `json:"issue_id,omitempty"`
RequirementID string `json:"requirement_id,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
func conversationDetailFrom(cv *chat.Conversation) conversationDetail {
d := conversationDetail{
ID: cv.ID.String(), ModelID: cv.ModelID.String(),
CreatedAt: cv.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: cv.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
if cv.Title != nil {
d.Title = *cv.Title
}
if cv.ProjectID != nil {
d.ProjectID = cv.ProjectID.String()
}
if cv.WorkspaceID != nil {
d.WorkspaceID = cv.WorkspaceID.String()
}
if cv.IssueID != nil {
d.IssueID = cv.IssueID.String()
}
if cv.RequirementID != nil {
d.RequirementID = cv.RequirementID.String()
}
return d
}
// mountRefs 是会话挂载维度的 UUID 集合(nil = 未挂载)。
type mountRefs struct {
ProjectID *uuid.UUID
WorkspaceID *uuid.UUID
IssueID *uuid.UUID
RequirementID *uuid.UUID
}
// resolveMountRefs 把 MCP 友好的挂载入参(project_slug + 编号)解析为 UUID,
// 并完成 project scope 校验。requirement/issue 编号必须随 project_slug 一起给。
func resolveMountRefs(ctx context.Context, deps ServerDeps, c project.Caller,
projectSlug, workspaceID string, requirementNumber, issueNumber int) (mountRefs, error) {
var refs mountRefs
if projectSlug == "" && (requirementNumber > 0 || issueNumber > 0) {
return refs, errs.New(errs.CodeInvalidInput,
"project_slug is required when requirement_number/issue_number is set")
}
if projectSlug != "" {
p, err := deps.Projects.Get(ctx, c, projectSlug)
if err != nil {
return refs, err
}
if err := CheckProjectFromCtx(ctx, p.ID); err != nil {
return refs, err
}
pid := p.ID
refs.ProjectID = &pid
if requirementNumber > 0 {
r, err := deps.Reqs.Get(ctx, c, projectSlug, requirementNumber)
if err != nil {
return refs, err
}
rid := r.ID
refs.RequirementID = &rid
}
if issueNumber > 0 {
i, err := deps.Issues.Get(ctx, c, projectSlug, issueNumber)
if err != nil {
return refs, err
}
iid := i.ID
refs.IssueID = &iid
}
}
if workspaceID != "" {
w, _, err := scopedWorkspace(ctx, deps, c, workspaceID)
if err != nil {
return refs, err
}
wid := w.ID
refs.WorkspaceID = &wid
}
return refs, nil
}
// scopedConversation 解析 conversation_id、取会话(service 层含 ownership 校验)
// 并做项目 scope 校验:ProjectID 为 nil 的个人会话不受 project scope 约束。
// limit 控制随会话返回的最近消息条数,仅 get_conversation 需要,其余入口传 1。
func scopedConversation(ctx context.Context, deps ServerDeps, userID uuid.UUID,
conversationID string, limit int) (*chat.Conversation, []chat.Message, error) {
convID, err := uuid.Parse(conversationID)
if err != nil {
return nil, nil, errs.Wrap(err, errs.CodeInvalidInput, "conversation_id parse")
}
cv, msgs, err := deps.Conversations.Get(ctx, userID, convID, limit)
if err != nil {
return nil, nil, err
}
if cv.ProjectID != nil {
if err := CheckProjectFromCtx(ctx, *cv.ProjectID); err != nil {
return nil, nil, err
}
}
return cv, msgs, nil
}
// ===== list_recent_messages =====
type listRecentMessagesIn struct {
ConversationID string `json:"conversation_id" jsonschema:"UUID of the conversation"`
BeforeID *int64 `json:"before_id,omitempty" jsonschema:"cursor: return messages with ID < this value"`
Limit int `json:"limit,omitempty" jsonschema:"max messages to return (default 50, max 200)"`
}
type messageSummary struct {
ID int64 `json:"id"`
Role string `json:"role"`
Content string `json:"content"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
}
type listRecentMessagesOut struct {
Messages []messageSummary `json:"messages"`
}
func messageSummaryFrom(m chat.Message) messageSummary {
return messageSummary{
ID: m.ID,
Role: string(m.Role),
Content: m.Content,
Status: string(m.Status),
CreatedAt: m.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
}
func registerListRecentMessages(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "list_recent_messages", Description: "List recent chat messages in a conversation."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in listRecentMessagesIn) (*mcpsdk.CallToolResult, listRecentMessagesOut, error) {
if err := CheckToolFromCtx(ctx, "list_recent_messages"); err != nil {
return nil, listRecentMessagesOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, listRecentMessagesOut{}, err
}
cv, _, err := scopedConversation(ctx, deps, c.UserID, in.ConversationID, 1)
if err != nil {
return nil, listRecentMessagesOut{}, err
}
limit := in.Limit
if limit <= 0 {
limit = 50
}
if limit > 200 {
limit = 200
}
msgs, err := deps.Messages.ListMessages(ctx, c.UserID, cv.ID, in.BeforeID, limit)
if err != nil {
return nil, listRecentMessagesOut{}, err
}
out := listRecentMessagesOut{Messages: make([]messageSummary, 0, len(msgs))}
for _, m := range msgs {
out.Messages = append(out.Messages, messageSummaryFrom(m))
}
return nil, out, nil
})
}
// ===== list_conversations =====
type listConversationsIn struct {
ProjectSlug string `json:"project_slug,omitempty" jsonschema:"filter by project"`
WorkspaceID string `json:"workspace_id,omitempty" jsonschema:"filter by workspace UUID"`
RequirementNumber int `json:"requirement_number,omitempty" jsonschema:"filter by requirement (needs project_slug)"`
IssueNumber int `json:"issue_number,omitempty" jsonschema:"filter by issue (needs project_slug)"`
TitleQuery string `json:"title_query,omitempty" jsonschema:"fuzzy title filter"`
BeforeID string `json:"before_id,omitempty" jsonschema:"cursor: conversation UUID"`
Limit int `json:"limit,omitempty" jsonschema:"max conversations to return (default 50, max 200)"`
}
type listConversationsOut struct {
Conversations []conversationDetail `json:"conversations"`
}
func registerListConversations(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "list_conversations", Description: "List the caller's chat conversations."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in listConversationsIn) (*mcpsdk.CallToolResult, listConversationsOut, error) {
if err := CheckToolFromCtx(ctx, "list_conversations"); err != nil {
return nil, listConversationsOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, listConversationsOut{}, err
}
refs, err := resolveMountRefs(ctx, deps, c, in.ProjectSlug, in.WorkspaceID, in.RequirementNumber, in.IssueNumber)
if err != nil {
return nil, listConversationsOut{}, err
}
filter := chat.ConversationFilter{
ProjectID: refs.ProjectID, WorkspaceID: refs.WorkspaceID,
IssueID: refs.IssueID, RequirementID: refs.RequirementID,
TitleQuery: in.TitleQuery,
}
limit := in.Limit
if limit <= 0 {
limit = 50
}
if limit > 200 {
limit = 200
}
filter.Limit = limit
if in.BeforeID != "" {
bid, err := uuid.Parse(in.BeforeID)
if err != nil {
return nil, listConversationsOut{}, errs.Wrap(err, errs.CodeInvalidInput, "before_id parse")
}
filter.BeforeID = &bid
}
cvs, err := deps.Conversations.List(ctx, c.UserID, filter)
if err != nil {
return nil, listConversationsOut{}, err
}
out := listConversationsOut{Conversations: make([]conversationDetail, 0, len(cvs))}
for i := range cvs {
// 与 list_acp_sessions 一致:scope 外项目挂载的会话静默跳过;
// ProjectID 为 nil 的个人会话不受 project scope 约束。
if cvs[i].ProjectID != nil {
if err := CheckProjectFromCtx(ctx, *cvs[i].ProjectID); err != nil {
continue
}
}
out.Conversations = append(out.Conversations, conversationDetailFrom(&cvs[i]))
}
return nil, out, nil
})
}
// ===== get_conversation =====
type getConversationIn struct {
ConversationID string `json:"conversation_id" jsonschema:"UUID of the conversation"`
Limit int `json:"limit,omitempty" jsonschema:"max recent messages to include (default 50, max 200)"`
}
type getConversationOut struct {
Conversation conversationDetail `json:"conversation"`
Messages []messageSummary `json:"messages"`
}
func registerGetConversation(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "get_conversation", Description: "Get a conversation with its recent messages."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in getConversationIn) (*mcpsdk.CallToolResult, getConversationOut, error) {
if err := CheckToolFromCtx(ctx, "get_conversation"); err != nil {
return nil, getConversationOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, getConversationOut{}, err
}
limit := in.Limit
if limit <= 0 {
limit = 50
}
if limit > 200 {
limit = 200
}
cv, msgs, err := scopedConversation(ctx, deps, c.UserID, in.ConversationID, limit)
if err != nil {
return nil, getConversationOut{}, err
}
out := getConversationOut{
Conversation: conversationDetailFrom(cv),
Messages: make([]messageSummary, 0, len(msgs)),
}
for _, m := range msgs {
out.Messages = append(out.Messages, messageSummaryFrom(m))
}
return nil, out, nil
})
}
// ===== create_conversation =====
type createConversationIn struct {
ModelID string `json:"model_id" jsonschema:"LLM model UUID (see list_models)"`
TemplateID string `json:"template_id,omitempty" jsonschema:"prompt template UUID"`
SystemPrompt string `json:"system_prompt,omitempty" jsonschema:"custom system prompt (overrides template)"`
ProjectSlug string `json:"project_slug,omitempty" jsonschema:"mount to a project"`
WorkspaceID string `json:"workspace_id,omitempty" jsonschema:"mount to a workspace UUID"`
RequirementNumber int `json:"requirement_number,omitempty" jsonschema:"mount to a requirement (needs project_slug)"`
IssueNumber int `json:"issue_number,omitempty" jsonschema:"mount to an issue (needs project_slug)"`
}
type conversationCreateOut struct {
OK bool `json:"ok"`
Conversation conversationDetail `json:"conversation"`
}
func registerCreateConversation(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "create_conversation", Description: "Create a new chat conversation."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in createConversationIn) (*mcpsdk.CallToolResult, conversationCreateOut, error) {
if err := CheckToolFromCtx(ctx, "create_conversation"); err != nil {
return nil, conversationCreateOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, conversationCreateOut{}, err
}
modelID, err := uuid.Parse(in.ModelID)
if err != nil {
return nil, conversationCreateOut{}, errs.Wrap(err, errs.CodeInvalidInput, "model_id parse")
}
refs, err := resolveMountRefs(ctx, deps, c, in.ProjectSlug, in.WorkspaceID, in.RequirementNumber, in.IssueNumber)
if err != nil {
return nil, conversationCreateOut{}, err
}
input := chat.CreateConversationInput{
ModelID: modelID,
ProjectID: refs.ProjectID, WorkspaceID: refs.WorkspaceID,
IssueID: refs.IssueID, RequirementID: refs.RequirementID,
}
if in.TemplateID != "" {
tid, err := uuid.Parse(in.TemplateID)
if err != nil {
return nil, conversationCreateOut{}, errs.Wrap(err, errs.CodeInvalidInput, "template_id parse")
}
input.TemplateID = &tid
}
if in.SystemPrompt != "" {
input.SystemPrompt = &in.SystemPrompt
}
cv, err := deps.Conversations.Create(ctx, c.UserID, input)
if err != nil {
return nil, conversationCreateOut{}, err
}
return nil, conversationCreateOut{OK: true, Conversation: conversationDetailFrom(cv)}, nil
})
}
// ===== update_conversation_title =====
type updateConversationTitleIn struct {
ConversationID string `json:"conversation_id" jsonschema:"UUID of the conversation"`
Title string `json:"title" jsonschema:"new title (non-empty)"`
}
func registerUpdateConversationTitle(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "update_conversation_title", Description: "Rename a conversation."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in updateConversationTitleIn) (*mcpsdk.CallToolResult, okOut, error) {
if err := CheckToolFromCtx(ctx, "update_conversation_title"); err != nil {
return nil, okOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, okOut{}, err
}
cv, _, err := scopedConversation(ctx, deps, c.UserID, in.ConversationID, 1)
if err != nil {
return nil, okOut{}, err
}
if err := deps.Conversations.UpdateTitle(ctx, c.UserID, cv.ID, in.Title); err != nil {
return nil, okOut{}, err
}
return nil, okOut{OK: true}, nil
})
}
// ===== delete_conversation =====
type deleteConversationIn struct {
ConversationID string `json:"conversation_id" jsonschema:"UUID of the conversation"`
}
func registerDeleteConversation(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "delete_conversation", Description: "Soft-delete a conversation."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in deleteConversationIn) (*mcpsdk.CallToolResult, okOut, error) {
if err := CheckToolFromCtx(ctx, "delete_conversation"); err != nil {
return nil, okOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, okOut{}, err
}
cv, _, err := scopedConversation(ctx, deps, c.UserID, in.ConversationID, 1)
if err != nil {
return nil, okOut{}, err
}
if err := deps.Conversations.SoftDelete(ctx, c.UserID, cv.ID); err != nil {
return nil, okOut{}, err
}
return nil, okOut{OK: true}, nil
})
}
// ===== send_message =====
type sendMessageIn struct {
ConversationID string `json:"conversation_id" jsonschema:"UUID of the conversation"`
Content string `json:"content" jsonschema:"message content (non-empty)"`
}
type sendMessageOut struct {
OK bool `json:"ok"`
UserMessageID int64 `json:"user_message_id"`
AssistantMessageID int64 `json:"assistant_message_id"`
}
func registerSendMessage(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "send_message", Description: "Send a user message to a conversation. The assistant reply is generated asynchronously; poll list_recent_messages for the result (attachments are not supported via MCP)."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in sendMessageIn) (*mcpsdk.CallToolResult, sendMessageOut, error) {
if err := CheckToolFromCtx(ctx, "send_message"); err != nil {
return nil, sendMessageOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, sendMessageOut{}, err
}
cv, _, err := scopedConversation(ctx, deps, c.UserID, in.ConversationID, 1)
if err != nil {
return nil, sendMessageOut{}, err
}
userMsgID, assistantMsgID, err := deps.Messages.Send(ctx, c.UserID, cv.ID, in.Content, nil)
if err != nil {
return nil, sendMessageOut{}, err
}
return nil, sendMessageOut{OK: true, UserMessageID: userMsgID, AssistantMessageID: assistantMsgID}, nil
})
}
// ===== list_prompt_templates =====
type templateDetail struct {
ID string `json:"id"`
Name string `json:"name"`
Scope string `json:"scope"`
Content string `json:"content"`
}
type listPromptTemplatesOut struct {
Templates []templateDetail `json:"templates"`
}
func registerListPromptTemplates(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "list_prompt_templates", Description: "List prompt templates visible to the caller (system + own)."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, _ struct{}) (*mcpsdk.CallToolResult, listPromptTemplatesOut, error) {
if err := CheckToolFromCtx(ctx, "list_prompt_templates"); err != nil {
return nil, listPromptTemplatesOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, listPromptTemplatesOut{}, err
}
ts, err := deps.Templates.List(ctx, c.UserID)
if err != nil {
return nil, listPromptTemplatesOut{}, err
}
out := listPromptTemplatesOut{Templates: make([]templateDetail, 0, len(ts))}
for _, t := range ts {
out.Templates = append(out.Templates, templateDetail{
ID: t.ID.String(), Name: t.Name, Scope: string(t.Scope), Content: t.Content,
})
}
return nil, out, nil
})
}
// ===== create_prompt_template =====
type createPromptTemplateIn struct {
Name string `json:"name" jsonschema:"template name (non-empty)"`
Content string `json:"content" jsonschema:"template content (non-empty)"`
Scope string `json:"scope,omitempty" jsonschema:"system|user (default user; system requires admin)"`
}
type templateCreateOut struct {
OK bool `json:"ok"`
Template templateDetail `json:"template"`
}
func registerCreatePromptTemplate(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "create_prompt_template", Description: "Create a prompt template. scope=system requires admin."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in createPromptTemplateIn) (*mcpsdk.CallToolResult, templateCreateOut, error) {
if err := CheckToolFromCtx(ctx, "create_prompt_template"); err != nil {
return nil, templateCreateOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, templateCreateOut{}, err
}
scope := chat.TemplateScope(in.Scope)
if scope == "" {
scope = chat.TemplateScopeUser
}
// 与 HTTP handler 对齐:system 模板仅 admin 可建(service 层不做此校验)。
if scope == chat.TemplateScopeSystem && !c.IsAdmin {
return nil, templateCreateOut{}, errs.New(errs.CodeForbidden, "admin only: system-scoped template")
}
t, err := deps.Templates.Create(ctx, c.UserID, in.Name, in.Content, scope)
if err != nil {
return nil, templateCreateOut{}, err
}
return nil, templateCreateOut{OK: true, Template: templateDetail{
ID: t.ID.String(), Name: t.Name, Scope: string(t.Scope), Content: t.Content,
}}, nil
})
}
// ===== update_prompt_template =====
type updatePromptTemplateIn struct {
TemplateID string `json:"template_id" jsonschema:"template UUID"`
Name string `json:"name" jsonschema:"new name (non-empty)"`
Content string `json:"content" jsonschema:"new content (non-empty)"`
}
func registerUpdatePromptTemplate(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "update_prompt_template", Description: "Update a prompt template (owner or system template)."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in updatePromptTemplateIn) (*mcpsdk.CallToolResult, okOut, error) {
if err := CheckToolFromCtx(ctx, "update_prompt_template"); err != nil {
return nil, okOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, okOut{}, err
}
tid, err := uuid.Parse(in.TemplateID)
if err != nil {
return nil, okOut{}, errs.Wrap(err, errs.CodeInvalidInput, "template_id parse")
}
if err := deps.Templates.Update(ctx, c.UserID, c.IsAdmin, tid, in.Name, in.Content); err != nil {
return nil, okOut{}, err
}
return nil, okOut{OK: true}, nil
})
}
// ===== delete_prompt_template =====
type deletePromptTemplateIn struct {
TemplateID string `json:"template_id" jsonschema:"template UUID"`
}
func registerDeletePromptTemplate(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "delete_prompt_template", Description: "Delete a prompt template (owner or system template)."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in deletePromptTemplateIn) (*mcpsdk.CallToolResult, okOut, error) {
if err := CheckToolFromCtx(ctx, "delete_prompt_template"); err != nil {
return nil, okOut{}, err
}
c, err := deps.Caller.Resolve(ctx)
if err != nil {
return nil, okOut{}, err
}
tid, err := uuid.Parse(in.TemplateID)
if err != nil {
return nil, okOut{}, errs.Wrap(err, errs.CodeInvalidInput, "template_id parse")
}
if err := deps.Templates.Delete(ctx, c.UserID, c.IsAdmin, tid); err != nil {
return nil, okOut{}, err
}
return nil, okOut{OK: true}, nil
})
}
// ===== list_models =====
type listModelsIn struct {
IncludeDisabled bool `json:"include_disabled,omitempty" jsonschema:"include disabled models (default false)"`
}
type modelSummary struct {
ID string `json:"id"`
ModelID string `json:"model_id"`
DisplayName string `json:"display_name"`
ContextWindow int `json:"context_window"`
MaxOutputTokens int `json:"max_output_tokens"`
Enabled bool `json:"enabled"`
}
type listModelsOut struct {
Models []modelSummary `json:"models"`
}
func registerListModels(srv *mcpsdk.Server, deps ServerDeps) {
mcpsdk.AddTool(srv,
&mcpsdk.Tool{Name: "list_models", Description: "List available LLM models (for create_conversation)."},
func(ctx context.Context, _ *mcpsdk.CallToolRequest, in listModelsIn) (*mcpsdk.CallToolResult, listModelsOut, error) {
if err := CheckToolFromCtx(ctx, "list_models"); err != nil {
return nil, listModelsOut{}, err
}
if _, err := deps.Caller.Resolve(ctx); err != nil {
return nil, listModelsOut{}, err
}
ms, err := deps.Endpoints.ListModels(ctx, !in.IncludeDisabled)
if err != nil {
return nil, listModelsOut{}, err
}
out := listModelsOut{Models: make([]modelSummary, 0, len(ms))}
for _, m := range ms {
out.Models = append(out.Models, modelSummary{
ID: m.ID.String(), ModelID: m.ModelID, DisplayName: m.DisplayName,
ContextWindow: m.ContextWindow, MaxOutputTokens: m.MaxOutputTokens,
Enabled: m.Enabled,
})
}
return nil, out, nil
})
}