feat: add agent skills and guardrails functionality

- 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.
This commit is contained in:
2026-03-30 23:35:54 +02:00
parent e6f00ce636
commit 3c1ca83dc9
14 changed files with 862 additions and 14 deletions

View File

@@ -173,6 +173,7 @@ func routes(logger *slog.Logger, cfg *config.Config, db *store.DB, provider ai.P
Calendar: tools.NewCalendarTool(db),
Meals: tools.NewMealsTool(db),
CRM: tools.NewCRMTool(db),
Skills: tools.NewSkillsTool(db, activeProjects),
}
mcpHandler := mcpserver.New(cfg.MCP, toolSet)

View File

@@ -11,7 +11,10 @@ import (
"git.warky.dev/wdevs/amcs/internal/tools"
)
const maxUploadBytes = 50 << 20
const (
maxUploadBytes = 100 << 20
multipartFormMemory = 32 << 20
)
func fileUploadHandler(files *tools.FilesTool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -53,7 +56,7 @@ func parseUploadRequest(r *http.Request) (tools.SaveFileDecodedInput, error) {
}
func parseMultipartUpload(r *http.Request) (tools.SaveFileDecodedInput, error) {
if err := r.ParseMultipartForm(maxUploadBytes); err != nil {
if err := r.ParseMultipartForm(multipartFormMemory); err != nil {
return tools.SaveFileDecodedInput{}, err
}

View File

@@ -50,8 +50,8 @@ func defaultConfig() Config {
Server: ServerConfig{
Host: "0.0.0.0",
Port: 8080,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
ReadTimeout: 10 * time.Minute,
WriteTimeout: 10 * time.Minute,
IdleTimeout: 60 * time.Second,
},
MCP: MCPConfig{

View File

@@ -33,6 +33,7 @@ type ToolSet struct {
Calendar *tools.CalendarTool
Meals *tools.MealsTool
CRM *tools.CRMTool
Skills *tools.SkillsTool
}
func New(cfg config.MCPConfig, toolSet ToolSet) http.Handler {
@@ -306,6 +307,69 @@ func New(cfg config.MCPConfig, toolSet ToolSet) http.Handler {
Description: "Append a stored thought to a contact's notes.",
}, toolSet.CRM.LinkThought)
// Agent Skills
addTool(server, &mcp.Tool{
Name: "add_skill",
Description: "Store a reusable agent skill (behavioural instruction or capability prompt).",
}, toolSet.Skills.AddSkill)
addTool(server, &mcp.Tool{
Name: "remove_skill",
Description: "Delete an agent skill by id.",
}, toolSet.Skills.RemoveSkill)
addTool(server, &mcp.Tool{
Name: "list_skills",
Description: "List all agent skills, optionally filtered by tag.",
}, toolSet.Skills.ListSkills)
// Agent Guardrails
addTool(server, &mcp.Tool{
Name: "add_guardrail",
Description: "Store a reusable agent guardrail (constraint or safety rule).",
}, toolSet.Skills.AddGuardrail)
addTool(server, &mcp.Tool{
Name: "remove_guardrail",
Description: "Delete an agent guardrail by id.",
}, toolSet.Skills.RemoveGuardrail)
addTool(server, &mcp.Tool{
Name: "list_guardrails",
Description: "List all agent guardrails, optionally filtered by tag or severity.",
}, toolSet.Skills.ListGuardrails)
// Project Skills & Guardrails
addTool(server, &mcp.Tool{
Name: "add_project_skill",
Description: "Link an agent skill to a project.",
}, toolSet.Skills.AddProjectSkill)
addTool(server, &mcp.Tool{
Name: "remove_project_skill",
Description: "Unlink an agent skill from a project.",
}, toolSet.Skills.RemoveProjectSkill)
addTool(server, &mcp.Tool{
Name: "list_project_skills",
Description: "List all skills linked to a project. Call this at the start of a project session to load existing agent behaviour instructions before generating new ones.",
}, toolSet.Skills.ListProjectSkills)
addTool(server, &mcp.Tool{
Name: "add_project_guardrail",
Description: "Link an agent guardrail to a project.",
}, toolSet.Skills.AddProjectGuardrail)
addTool(server, &mcp.Tool{
Name: "remove_project_guardrail",
Description: "Unlink an agent guardrail from a project.",
}, toolSet.Skills.RemoveProjectGuardrail)
addTool(server, &mcp.Tool{
Name: "list_project_guardrails",
Description: "List all guardrails linked to a project. Call this at the start of a project session to load existing agent constraints before generating new ones.",
}, toolSet.Skills.ListProjectGuardrails)
return mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
return server
}, &mcp.StreamableHTTPOptions{

294
internal/store/skills.go Normal file
View File

@@ -0,0 +1,294 @@
package store
import (
"context"
"fmt"
"strings"
"github.com/google/uuid"
ext "git.warky.dev/wdevs/amcs/internal/types"
)
// Agent Skills
func (db *DB) AddSkill(ctx context.Context, skill ext.AgentSkill) (ext.AgentSkill, error) {
if skill.Tags == nil {
skill.Tags = []string{}
}
row := db.pool.QueryRow(ctx, `
insert into agent_skills (name, description, content, tags)
values ($1, $2, $3, $4)
returning id, created_at, updated_at
`, skill.Name, skill.Description, skill.Content, skill.Tags)
created := skill
if err := row.Scan(&created.ID, &created.CreatedAt, &created.UpdatedAt); err != nil {
return ext.AgentSkill{}, fmt.Errorf("insert agent skill: %w", err)
}
return created, nil
}
func (db *DB) RemoveSkill(ctx context.Context, id uuid.UUID) error {
tag, err := db.pool.Exec(ctx, `delete from agent_skills where id = $1`, id)
if err != nil {
return fmt.Errorf("delete agent skill: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("skill not found")
}
return nil
}
func (db *DB) ListSkills(ctx context.Context, tag string) ([]ext.AgentSkill, error) {
q := `select id, name, description, content, tags, created_at, updated_at from agent_skills`
args := []any{}
if t := strings.TrimSpace(tag); t != "" {
args = append(args, t)
q += fmt.Sprintf(" where $%d = any(tags)", len(args))
}
q += " order by name"
rows, err := db.pool.Query(ctx, q, args...)
if err != nil {
return nil, fmt.Errorf("list agent skills: %w", err)
}
defer rows.Close()
var skills []ext.AgentSkill
for rows.Next() {
var s ext.AgentSkill
var desc *string
if err := rows.Scan(&s.ID, &s.Name, &desc, &s.Content, &s.Tags, &s.CreatedAt, &s.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan agent skill: %w", err)
}
s.Description = strVal(desc)
if s.Tags == nil {
s.Tags = []string{}
}
skills = append(skills, s)
}
return skills, rows.Err()
}
func (db *DB) GetSkill(ctx context.Context, id uuid.UUID) (ext.AgentSkill, error) {
row := db.pool.QueryRow(ctx, `
select id, name, description, content, tags, created_at, updated_at
from agent_skills where id = $1
`, id)
var s ext.AgentSkill
var desc *string
if err := row.Scan(&s.ID, &s.Name, &desc, &s.Content, &s.Tags, &s.CreatedAt, &s.UpdatedAt); err != nil {
return ext.AgentSkill{}, fmt.Errorf("get agent skill: %w", err)
}
s.Description = strVal(desc)
if s.Tags == nil {
s.Tags = []string{}
}
return s, nil
}
// Agent Guardrails
func (db *DB) AddGuardrail(ctx context.Context, g ext.AgentGuardrail) (ext.AgentGuardrail, error) {
if g.Tags == nil {
g.Tags = []string{}
}
if g.Severity == "" {
g.Severity = "medium"
}
row := db.pool.QueryRow(ctx, `
insert into agent_guardrails (name, description, content, severity, tags)
values ($1, $2, $3, $4, $5)
returning id, created_at, updated_at
`, g.Name, g.Description, g.Content, g.Severity, g.Tags)
created := g
if err := row.Scan(&created.ID, &created.CreatedAt, &created.UpdatedAt); err != nil {
return ext.AgentGuardrail{}, fmt.Errorf("insert agent guardrail: %w", err)
}
return created, nil
}
func (db *DB) RemoveGuardrail(ctx context.Context, id uuid.UUID) error {
tag, err := db.pool.Exec(ctx, `delete from agent_guardrails where id = $1`, id)
if err != nil {
return fmt.Errorf("delete agent guardrail: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("guardrail not found")
}
return nil
}
func (db *DB) ListGuardrails(ctx context.Context, tag, severity string) ([]ext.AgentGuardrail, error) {
args := []any{}
conditions := []string{}
if t := strings.TrimSpace(tag); t != "" {
args = append(args, t)
conditions = append(conditions, fmt.Sprintf("$%d = any(tags)", len(args)))
}
if s := strings.TrimSpace(severity); s != "" {
args = append(args, s)
conditions = append(conditions, fmt.Sprintf("severity = $%d", len(args)))
}
q := `select id, name, description, content, severity, tags, created_at, updated_at from agent_guardrails`
if len(conditions) > 0 {
q += " where " + strings.Join(conditions, " and ")
}
q += " order by name"
rows, err := db.pool.Query(ctx, q, args...)
if err != nil {
return nil, fmt.Errorf("list agent guardrails: %w", err)
}
defer rows.Close()
var guardrails []ext.AgentGuardrail
for rows.Next() {
var g ext.AgentGuardrail
var desc *string
if err := rows.Scan(&g.ID, &g.Name, &desc, &g.Content, &g.Severity, &g.Tags, &g.CreatedAt, &g.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan agent guardrail: %w", err)
}
g.Description = strVal(desc)
if g.Tags == nil {
g.Tags = []string{}
}
guardrails = append(guardrails, g)
}
return guardrails, rows.Err()
}
func (db *DB) GetGuardrail(ctx context.Context, id uuid.UUID) (ext.AgentGuardrail, error) {
row := db.pool.QueryRow(ctx, `
select id, name, description, content, severity, tags, created_at, updated_at
from agent_guardrails where id = $1
`, id)
var g ext.AgentGuardrail
var desc *string
if err := row.Scan(&g.ID, &g.Name, &desc, &g.Content, &g.Severity, &g.Tags, &g.CreatedAt, &g.UpdatedAt); err != nil {
return ext.AgentGuardrail{}, fmt.Errorf("get agent guardrail: %w", err)
}
g.Description = strVal(desc)
if g.Tags == nil {
g.Tags = []string{}
}
return g, nil
}
// Project Skills
func (db *DB) AddProjectSkill(ctx context.Context, projectID, skillID uuid.UUID) error {
_, err := db.pool.Exec(ctx, `
insert into project_skills (project_id, skill_id)
values ($1, $2)
on conflict do nothing
`, projectID, skillID)
if err != nil {
return fmt.Errorf("add project skill: %w", err)
}
return nil
}
func (db *DB) RemoveProjectSkill(ctx context.Context, projectID, skillID uuid.UUID) error {
tag, err := db.pool.Exec(ctx, `
delete from project_skills where project_id = $1 and skill_id = $2
`, projectID, skillID)
if err != nil {
return fmt.Errorf("remove project skill: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("project skill link not found")
}
return nil
}
func (db *DB) ListProjectSkills(ctx context.Context, projectID uuid.UUID) ([]ext.AgentSkill, error) {
rows, err := db.pool.Query(ctx, `
select s.id, s.name, s.description, s.content, s.tags, s.created_at, s.updated_at
from agent_skills s
join project_skills ps on ps.skill_id = s.id
where ps.project_id = $1
order by s.name
`, projectID)
if err != nil {
return nil, fmt.Errorf("list project skills: %w", err)
}
defer rows.Close()
var skills []ext.AgentSkill
for rows.Next() {
var s ext.AgentSkill
var desc *string
if err := rows.Scan(&s.ID, &s.Name, &desc, &s.Content, &s.Tags, &s.CreatedAt, &s.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan project skill: %w", err)
}
s.Description = strVal(desc)
if s.Tags == nil {
s.Tags = []string{}
}
skills = append(skills, s)
}
return skills, rows.Err()
}
// Project Guardrails
func (db *DB) AddProjectGuardrail(ctx context.Context, projectID, guardrailID uuid.UUID) error {
_, err := db.pool.Exec(ctx, `
insert into project_guardrails (project_id, guardrail_id)
values ($1, $2)
on conflict do nothing
`, projectID, guardrailID)
if err != nil {
return fmt.Errorf("add project guardrail: %w", err)
}
return nil
}
func (db *DB) RemoveProjectGuardrail(ctx context.Context, projectID, guardrailID uuid.UUID) error {
tag, err := db.pool.Exec(ctx, `
delete from project_guardrails where project_id = $1 and guardrail_id = $2
`, projectID, guardrailID)
if err != nil {
return fmt.Errorf("remove project guardrail: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("project guardrail link not found")
}
return nil
}
func (db *DB) ListProjectGuardrails(ctx context.Context, projectID uuid.UUID) ([]ext.AgentGuardrail, error) {
rows, err := db.pool.Query(ctx, `
select g.id, g.name, g.description, g.content, g.severity, g.tags, g.created_at, g.updated_at
from agent_guardrails g
join project_guardrails pg on pg.guardrail_id = g.id
where pg.project_id = $1
order by g.name
`, projectID)
if err != nil {
return nil, fmt.Errorf("list project guardrails: %w", err)
}
defer rows.Close()
var guardrails []ext.AgentGuardrail
for rows.Next() {
var g ext.AgentGuardrail
var desc *string
if err := rows.Scan(&g.ID, &g.Name, &desc, &g.Content, &g.Severity, &g.Tags, &g.CreatedAt, &g.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan project guardrail: %w", err)
}
g.Description = strVal(desc)
if g.Tags == nil {
g.Tags = []string{}
}
guardrails = append(guardrails, g)
}
return guardrails, rows.Err()
}

322
internal/tools/skills.go Normal file
View File

@@ -0,0 +1,322 @@
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
}

View File

@@ -209,7 +209,30 @@ type Opportunity struct {
}
type ContactHistory struct {
Contact ProfessionalContact `json:"contact"`
Interactions []ContactInteraction `json:"interactions"`
Opportunities []Opportunity `json:"opportunities"`
Contact ProfessionalContact `json:"contact"`
Interactions []ContactInteraction `json:"interactions"`
Opportunities []Opportunity `json:"opportunities"`
}
// Agent Skills & Guardrails
type AgentSkill struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Content string `json:"content"`
Tags []string `json:"tags"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type AgentGuardrail struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Content string `json:"content"`
Severity string `json:"severity"`
Tags []string `json:"tags"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}