You've already forked agentic-coding-workflow
完善 MCP 工具
This commit is contained in:
+565
-8
@@ -6,14 +6,128 @@ import (
|
||||
|
||||
"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 工具。
|
||||
// MVP 只有一个:list_recent_messages。
|
||||
// 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
|
||||
}
|
||||
|
||||
// ===== 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"`
|
||||
@@ -30,6 +144,16 @@ 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."},
|
||||
@@ -62,12 +186,445 @@ func registerListRecentMessages(srv *mcpsdk.Server, deps ServerDeps) {
|
||||
|
||||
out := listRecentMessagesOut{Messages: make([]messageSummary, 0, len(msgs))}
|
||||
for _, m := range msgs {
|
||||
out.Messages = append(out.Messages, 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"),
|
||||
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 {
|
||||
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
|
||||
}
|
||||
convID, err := uuid.Parse(in.ConversationID)
|
||||
if err != nil {
|
||||
return nil, getConversationOut{}, errs.Wrap(err, errs.CodeInvalidInput, "conversation_id parse")
|
||||
}
|
||||
limit := in.Limit
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
if limit > 200 {
|
||||
limit = 200
|
||||
}
|
||||
cv, msgs, err := deps.Conversations.Get(ctx, c.UserID, convID, 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
|
||||
}
|
||||
convID, err := uuid.Parse(in.ConversationID)
|
||||
if err != nil {
|
||||
return nil, okOut{}, errs.Wrap(err, errs.CodeInvalidInput, "conversation_id parse")
|
||||
}
|
||||
if err := deps.Conversations.UpdateTitle(ctx, c.UserID, convID, 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
|
||||
}
|
||||
convID, err := uuid.Parse(in.ConversationID)
|
||||
if err != nil {
|
||||
return nil, okOut{}, errs.Wrap(err, errs.CodeInvalidInput, "conversation_id parse")
|
||||
}
|
||||
if err := deps.Conversations.SoftDelete(ctx, c.UserID, convID); 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
|
||||
}
|
||||
convID, err := uuid.Parse(in.ConversationID)
|
||||
if err != nil {
|
||||
return nil, sendMessageOut{}, errs.Wrap(err, errs.CodeInvalidInput, "conversation_id parse")
|
||||
}
|
||||
userMsgID, assistantMsgID, err := deps.Messages.Send(ctx, c.UserID, convID, 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
|
||||
|
||||
Reference in New Issue
Block a user