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 }