- Introduced new tools for managing agent skills and guardrails, including add, remove, and list operations. - Updated README.md to document new commands and usage patterns for skills and guardrails. - Enhanced server configuration to support longer read and write timeouts. - Increased maximum upload size for files to 100 MB and adjusted related configurations. - Created database migrations for agent skills, guardrails, and their associations with projects. - Updated relevant code files to integrate new skills and guardrails into the application logic.
323 lines
11 KiB
Go
323 lines
11 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
|
|
"git.warky.dev/wdevs/amcs/internal/session"
|
|
"git.warky.dev/wdevs/amcs/internal/store"
|
|
ext "git.warky.dev/wdevs/amcs/internal/types"
|
|
)
|
|
|
|
type SkillsTool struct {
|
|
store *store.DB
|
|
sessions *session.ActiveProjects
|
|
}
|
|
|
|
func NewSkillsTool(db *store.DB, sessions *session.ActiveProjects) *SkillsTool {
|
|
return &SkillsTool{store: db, sessions: sessions}
|
|
}
|
|
|
|
// add_skill
|
|
|
|
type AddSkillInput struct {
|
|
Name string `json:"name" jsonschema:"unique skill name"`
|
|
Description string `json:"description,omitempty" jsonschema:"short description of what the skill does"`
|
|
Content string `json:"content" jsonschema:"the full skill instruction or prompt content"`
|
|
Tags []string `json:"tags,omitempty" jsonschema:"optional tags for grouping or filtering"`
|
|
}
|
|
|
|
type AddSkillOutput struct {
|
|
Skill ext.AgentSkill `json:"skill"`
|
|
}
|
|
|
|
func (t *SkillsTool) AddSkill(ctx context.Context, _ *mcp.CallToolRequest, in AddSkillInput) (*mcp.CallToolResult, AddSkillOutput, error) {
|
|
if strings.TrimSpace(in.Name) == "" {
|
|
return nil, AddSkillOutput{}, errInvalidInput("name is required")
|
|
}
|
|
if strings.TrimSpace(in.Content) == "" {
|
|
return nil, AddSkillOutput{}, errInvalidInput("content is required")
|
|
}
|
|
if in.Tags == nil {
|
|
in.Tags = []string{}
|
|
}
|
|
skill, err := t.store.AddSkill(ctx, ext.AgentSkill{
|
|
Name: strings.TrimSpace(in.Name),
|
|
Description: strings.TrimSpace(in.Description),
|
|
Content: strings.TrimSpace(in.Content),
|
|
Tags: in.Tags,
|
|
})
|
|
if err != nil {
|
|
return nil, AddSkillOutput{}, err
|
|
}
|
|
return nil, AddSkillOutput{Skill: skill}, nil
|
|
}
|
|
|
|
// remove_skill
|
|
|
|
type RemoveSkillInput struct {
|
|
ID uuid.UUID `json:"id" jsonschema:"skill id to remove"`
|
|
}
|
|
|
|
type RemoveSkillOutput struct {
|
|
Removed bool `json:"removed"`
|
|
}
|
|
|
|
func (t *SkillsTool) RemoveSkill(ctx context.Context, _ *mcp.CallToolRequest, in RemoveSkillInput) (*mcp.CallToolResult, RemoveSkillOutput, error) {
|
|
if err := t.store.RemoveSkill(ctx, in.ID); err != nil {
|
|
return nil, RemoveSkillOutput{}, err
|
|
}
|
|
return nil, RemoveSkillOutput{Removed: true}, nil
|
|
}
|
|
|
|
// list_skills
|
|
|
|
type ListSkillsInput struct {
|
|
Tag string `json:"tag,omitempty" jsonschema:"filter by tag"`
|
|
}
|
|
|
|
type ListSkillsOutput struct {
|
|
Skills []ext.AgentSkill `json:"skills"`
|
|
}
|
|
|
|
func (t *SkillsTool) ListSkills(ctx context.Context, _ *mcp.CallToolRequest, in ListSkillsInput) (*mcp.CallToolResult, ListSkillsOutput, error) {
|
|
skills, err := t.store.ListSkills(ctx, in.Tag)
|
|
if err != nil {
|
|
return nil, ListSkillsOutput{}, err
|
|
}
|
|
if skills == nil {
|
|
skills = []ext.AgentSkill{}
|
|
}
|
|
return nil, ListSkillsOutput{Skills: skills}, nil
|
|
}
|
|
|
|
// add_guardrail
|
|
|
|
type AddGuardrailInput struct {
|
|
Name string `json:"name" jsonschema:"unique guardrail name"`
|
|
Description string `json:"description,omitempty" jsonschema:"short description of what the guardrail enforces"`
|
|
Content string `json:"content" jsonschema:"the full guardrail rule or constraint content"`
|
|
Severity string `json:"severity,omitempty" jsonschema:"one of: low, medium, high, critical (default medium)"`
|
|
Tags []string `json:"tags,omitempty" jsonschema:"optional tags for grouping or filtering"`
|
|
}
|
|
|
|
type AddGuardrailOutput struct {
|
|
Guardrail ext.AgentGuardrail `json:"guardrail"`
|
|
}
|
|
|
|
func (t *SkillsTool) AddGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in AddGuardrailInput) (*mcp.CallToolResult, AddGuardrailOutput, error) {
|
|
if strings.TrimSpace(in.Name) == "" {
|
|
return nil, AddGuardrailOutput{}, errInvalidInput("name is required")
|
|
}
|
|
if strings.TrimSpace(in.Content) == "" {
|
|
return nil, AddGuardrailOutput{}, errInvalidInput("content is required")
|
|
}
|
|
severity := strings.TrimSpace(in.Severity)
|
|
if severity == "" {
|
|
severity = "medium"
|
|
}
|
|
switch severity {
|
|
case "low", "medium", "high", "critical":
|
|
default:
|
|
return nil, AddGuardrailOutput{}, errInvalidInput("severity must be one of: low, medium, high, critical")
|
|
}
|
|
if in.Tags == nil {
|
|
in.Tags = []string{}
|
|
}
|
|
guardrail, err := t.store.AddGuardrail(ctx, ext.AgentGuardrail{
|
|
Name: strings.TrimSpace(in.Name),
|
|
Description: strings.TrimSpace(in.Description),
|
|
Content: strings.TrimSpace(in.Content),
|
|
Severity: severity,
|
|
Tags: in.Tags,
|
|
})
|
|
if err != nil {
|
|
return nil, AddGuardrailOutput{}, err
|
|
}
|
|
return nil, AddGuardrailOutput{Guardrail: guardrail}, nil
|
|
}
|
|
|
|
// remove_guardrail
|
|
|
|
type RemoveGuardrailInput struct {
|
|
ID uuid.UUID `json:"id" jsonschema:"guardrail id to remove"`
|
|
}
|
|
|
|
type RemoveGuardrailOutput struct {
|
|
Removed bool `json:"removed"`
|
|
}
|
|
|
|
func (t *SkillsTool) RemoveGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in RemoveGuardrailInput) (*mcp.CallToolResult, RemoveGuardrailOutput, error) {
|
|
if err := t.store.RemoveGuardrail(ctx, in.ID); err != nil {
|
|
return nil, RemoveGuardrailOutput{}, err
|
|
}
|
|
return nil, RemoveGuardrailOutput{Removed: true}, nil
|
|
}
|
|
|
|
// list_guardrails
|
|
|
|
type ListGuardrailsInput struct {
|
|
Tag string `json:"tag,omitempty" jsonschema:"filter by tag"`
|
|
Severity string `json:"severity,omitempty" jsonschema:"filter by severity: low, medium, high, or critical"`
|
|
}
|
|
|
|
type ListGuardrailsOutput struct {
|
|
Guardrails []ext.AgentGuardrail `json:"guardrails"`
|
|
}
|
|
|
|
func (t *SkillsTool) ListGuardrails(ctx context.Context, _ *mcp.CallToolRequest, in ListGuardrailsInput) (*mcp.CallToolResult, ListGuardrailsOutput, error) {
|
|
guardrails, err := t.store.ListGuardrails(ctx, in.Tag, in.Severity)
|
|
if err != nil {
|
|
return nil, ListGuardrailsOutput{}, err
|
|
}
|
|
if guardrails == nil {
|
|
guardrails = []ext.AgentGuardrail{}
|
|
}
|
|
return nil, ListGuardrailsOutput{Guardrails: guardrails}, nil
|
|
}
|
|
|
|
// add_project_skill
|
|
|
|
type AddProjectSkillInput struct {
|
|
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
|
SkillID uuid.UUID `json:"skill_id" jsonschema:"skill id to link"`
|
|
}
|
|
|
|
type AddProjectSkillOutput struct {
|
|
ProjectID uuid.UUID `json:"project_id"`
|
|
SkillID uuid.UUID `json:"skill_id"`
|
|
}
|
|
|
|
func (t *SkillsTool) AddProjectSkill(ctx context.Context, req *mcp.CallToolRequest, in AddProjectSkillInput) (*mcp.CallToolResult, AddProjectSkillOutput, error) {
|
|
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
|
if err != nil {
|
|
return nil, AddProjectSkillOutput{}, err
|
|
}
|
|
if err := t.store.AddProjectSkill(ctx, project.ID, in.SkillID); err != nil {
|
|
return nil, AddProjectSkillOutput{}, err
|
|
}
|
|
return nil, AddProjectSkillOutput{ProjectID: project.ID, SkillID: in.SkillID}, nil
|
|
}
|
|
|
|
// remove_project_skill
|
|
|
|
type RemoveProjectSkillInput struct {
|
|
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
|
SkillID uuid.UUID `json:"skill_id" jsonschema:"skill id to unlink"`
|
|
}
|
|
|
|
type RemoveProjectSkillOutput struct {
|
|
Removed bool `json:"removed"`
|
|
}
|
|
|
|
func (t *SkillsTool) RemoveProjectSkill(ctx context.Context, req *mcp.CallToolRequest, in RemoveProjectSkillInput) (*mcp.CallToolResult, RemoveProjectSkillOutput, error) {
|
|
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
|
if err != nil {
|
|
return nil, RemoveProjectSkillOutput{}, err
|
|
}
|
|
if err := t.store.RemoveProjectSkill(ctx, project.ID, in.SkillID); err != nil {
|
|
return nil, RemoveProjectSkillOutput{}, err
|
|
}
|
|
return nil, RemoveProjectSkillOutput{Removed: true}, nil
|
|
}
|
|
|
|
// list_project_skills
|
|
|
|
type ListProjectSkillsInput struct {
|
|
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
|
}
|
|
|
|
type ListProjectSkillsOutput struct {
|
|
ProjectID uuid.UUID `json:"project_id"`
|
|
Skills []ext.AgentSkill `json:"skills"`
|
|
}
|
|
|
|
func (t *SkillsTool) ListProjectSkills(ctx context.Context, req *mcp.CallToolRequest, in ListProjectSkillsInput) (*mcp.CallToolResult, ListProjectSkillsOutput, error) {
|
|
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
|
if err != nil {
|
|
return nil, ListProjectSkillsOutput{}, err
|
|
}
|
|
skills, err := t.store.ListProjectSkills(ctx, project.ID)
|
|
if err != nil {
|
|
return nil, ListProjectSkillsOutput{}, err
|
|
}
|
|
if skills == nil {
|
|
skills = []ext.AgentSkill{}
|
|
}
|
|
return nil, ListProjectSkillsOutput{ProjectID: project.ID, Skills: skills}, nil
|
|
}
|
|
|
|
// add_project_guardrail
|
|
|
|
type AddProjectGuardrailInput struct {
|
|
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
|
GuardrailID uuid.UUID `json:"guardrail_id" jsonschema:"guardrail id to link"`
|
|
}
|
|
|
|
type AddProjectGuardrailOutput struct {
|
|
ProjectID uuid.UUID `json:"project_id"`
|
|
GuardrailID uuid.UUID `json:"guardrail_id"`
|
|
}
|
|
|
|
func (t *SkillsTool) AddProjectGuardrail(ctx context.Context, req *mcp.CallToolRequest, in AddProjectGuardrailInput) (*mcp.CallToolResult, AddProjectGuardrailOutput, error) {
|
|
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
|
if err != nil {
|
|
return nil, AddProjectGuardrailOutput{}, err
|
|
}
|
|
if err := t.store.AddProjectGuardrail(ctx, project.ID, in.GuardrailID); err != nil {
|
|
return nil, AddProjectGuardrailOutput{}, err
|
|
}
|
|
return nil, AddProjectGuardrailOutput{ProjectID: project.ID, GuardrailID: in.GuardrailID}, nil
|
|
}
|
|
|
|
// remove_project_guardrail
|
|
|
|
type RemoveProjectGuardrailInput struct {
|
|
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
|
GuardrailID uuid.UUID `json:"guardrail_id" jsonschema:"guardrail id to unlink"`
|
|
}
|
|
|
|
type RemoveProjectGuardrailOutput struct {
|
|
Removed bool `json:"removed"`
|
|
}
|
|
|
|
func (t *SkillsTool) RemoveProjectGuardrail(ctx context.Context, req *mcp.CallToolRequest, in RemoveProjectGuardrailInput) (*mcp.CallToolResult, RemoveProjectGuardrailOutput, error) {
|
|
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
|
if err != nil {
|
|
return nil, RemoveProjectGuardrailOutput{}, err
|
|
}
|
|
if err := t.store.RemoveProjectGuardrail(ctx, project.ID, in.GuardrailID); err != nil {
|
|
return nil, RemoveProjectGuardrailOutput{}, err
|
|
}
|
|
return nil, RemoveProjectGuardrailOutput{Removed: true}, nil
|
|
}
|
|
|
|
// list_project_guardrails
|
|
|
|
type ListProjectGuardrailsInput struct {
|
|
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
|
}
|
|
|
|
type ListProjectGuardrailsOutput struct {
|
|
ProjectID uuid.UUID `json:"project_id"`
|
|
Guardrails []ext.AgentGuardrail `json:"guardrails"`
|
|
}
|
|
|
|
func (t *SkillsTool) ListProjectGuardrails(ctx context.Context, req *mcp.CallToolRequest, in ListProjectGuardrailsInput) (*mcp.CallToolResult, ListProjectGuardrailsOutput, error) {
|
|
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
|
if err != nil {
|
|
return nil, ListProjectGuardrailsOutput{}, err
|
|
}
|
|
guardrails, err := t.store.ListProjectGuardrails(ctx, project.ID)
|
|
if err != nil {
|
|
return nil, ListProjectGuardrailsOutput{}, err
|
|
}
|
|
if guardrails == nil {
|
|
guardrails = []ext.AgentGuardrail{}
|
|
}
|
|
return nil, ListProjectGuardrailsOutput{ProjectID: project.ID, Guardrails: guardrails}, nil
|
|
}
|