Skip to content

Commit 0968330

Browse files
authored
Merge pull request #2 from evnsio/chris/add-catalog-endpoints
Add comprehensive catalog management capabilities to MCP server
2 parents f9fbe47 + 7ea2620 commit 0968330

File tree

7 files changed

+592
-4
lines changed

7 files changed

+592
-4
lines changed

.claude/settings.local.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@
5050
"Bash(golangci-lint run:*)",
5151
"Bash(golangci-lint:*)",
5252
"Bash(pkill:*)",
53-
"Bash(/Users/tomwentworth/incidentio-mcp-golang/test_auth.sh)"
53+
"Bash(/Users/tomwentworth/incidentio-mcp-golang/test_auth.sh)",
54+
"Bash(gh pr create:*)",
55+
"Bash(git remote add:*)",
56+
"Bash(gh auth:*)",
57+
"Bash(go fmt:*)",
58+
"Bash(git commit:*)",
59+
"Bash(git reset:*)"
5460
],
5561
"deny": []
5662
},

cmd/mcp-server/main.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,19 @@ func (s *MCPServer) registerTools() {
6565
s.tools["assign_incident_role"] = tools.NewAssignIncidentRoleTool(client)
6666
s.tools["list_severities"] = tools.NewListSeveritiesTool(client)
6767
s.tools["get_severity"] = tools.NewGetSeverityTool(client)
68+
69+
// Register Catalog tools
70+
s.tools["list_catalog_types"] = tools.NewListCatalogTypesTool(client)
71+
s.tools["list_catalog_entries"] = tools.NewListCatalogEntriesTool(client)
72+
s.tools["update_catalog_entry"] = tools.NewUpdateCatalogEntryTool(client)
6873
}
6974

7075
func (s *MCPServer) start(ctx context.Context) {
7176
// Log startup message to stderr (stdout is reserved for MCP protocol)
7277
log.SetOutput(os.Stderr)
7378
log.Println("Starting incident.io MCP server...")
7479
log.Printf("Registered %d tools", len(s.tools))
75-
80+
7681
encoder := json.NewEncoder(os.Stdout)
7782
decoder := json.NewDecoder(os.Stdin)
7883

@@ -284,7 +289,7 @@ func (s *MCPServer) handleToolCall(msg *mcp.Message) *mcp.Message {
284289
},
285290
}
286291
}
287-
292+
288293
log.Printf("Tool executed successfully: %s", toolName)
289294

290295
return &mcp.Message{

internal/incidentio/catalog.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package incidentio
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/url"
7+
)
8+
9+
// ListCatalogTypes returns all catalog types
10+
func (c *Client) ListCatalogTypes() (*ListCatalogTypesResponse, error) {
11+
// Catalog API uses V3, need to temporarily change the base URL
12+
originalBaseURL := c.BaseURL()
13+
c.SetBaseURL("https://api.incident.io/v3")
14+
defer func() { c.SetBaseURL(originalBaseURL) }()
15+
16+
respBody, err := c.doRequest("GET", "/catalog_types", nil, nil)
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
var response ListCatalogTypesResponse
22+
if err := json.Unmarshal(respBody, &response); err != nil {
23+
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
24+
}
25+
26+
return &response, nil
27+
}
28+
29+
// ListCatalogEntriesOptions represents options for listing catalog entries
30+
type ListCatalogEntriesOptions struct {
31+
CatalogTypeID string
32+
PageSize int
33+
After string
34+
Identifier string
35+
}
36+
37+
// ListCatalogEntries returns catalog entries for a given type
38+
func (c *Client) ListCatalogEntries(opts ListCatalogEntriesOptions) (*ListCatalogEntriesResponse, error) {
39+
// Catalog API uses V3, need to temporarily change the base URL
40+
originalBaseURL := c.BaseURL()
41+
c.SetBaseURL("https://api.incident.io/v3")
42+
defer func() { c.SetBaseURL(originalBaseURL) }()
43+
44+
params := url.Values{}
45+
if opts.CatalogTypeID != "" {
46+
params.Set("catalog_type_id", opts.CatalogTypeID)
47+
}
48+
if opts.PageSize > 0 {
49+
params.Set("page_size", fmt.Sprintf("%d", opts.PageSize))
50+
}
51+
if opts.After != "" {
52+
params.Set("after", opts.After)
53+
}
54+
if opts.Identifier != "" {
55+
params.Set("identifier", opts.Identifier)
56+
}
57+
58+
respBody, err := c.doRequest("GET", "/catalog_entries", params, nil)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
var response ListCatalogEntriesResponse
64+
if err := json.Unmarshal(respBody, &response); err != nil {
65+
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
66+
}
67+
68+
return &response, nil
69+
}
70+
71+
// UpdateCatalogEntry updates a catalog entry by ID
72+
func (c *Client) UpdateCatalogEntry(id string, req UpdateCatalogEntryRequest) (*CatalogEntry, error) {
73+
// Catalog API uses V3, need to temporarily change the base URL
74+
originalBaseURL := c.BaseURL()
75+
c.SetBaseURL("https://api.incident.io/v3")
76+
defer func() { c.SetBaseURL(originalBaseURL) }()
77+
78+
respBody, err := c.doRequest("PUT", fmt.Sprintf("/catalog_entries/%s", id), nil, req)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
var response struct {
84+
CatalogEntry CatalogEntry `json:"catalog_entry"`
85+
}
86+
if err := json.Unmarshal(respBody, &response); err != nil {
87+
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
88+
}
89+
90+
return &response.CatalogEntry, nil
91+
}
92+
93+
// GetCatalogEntry retrieves a specific catalog entry by ID
94+
func (c *Client) GetCatalogEntry(id string) (*CatalogEntry, error) {
95+
// Catalog API uses V3, need to temporarily change the base URL
96+
originalBaseURL := c.BaseURL()
97+
c.SetBaseURL("https://api.incident.io/v3")
98+
defer func() { c.SetBaseURL(originalBaseURL) }()
99+
100+
respBody, err := c.doRequest("GET", fmt.Sprintf("/catalog_entries/%s", id), nil, nil)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
var response struct {
106+
CatalogEntry CatalogEntry `json:"catalog_entry"`
107+
}
108+
if err := json.Unmarshal(respBody, &response); err != nil {
109+
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
110+
}
111+
112+
return &response.CatalogEntry, nil
113+
}

internal/incidentio/types.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,71 @@ type ListResponse struct {
270270
TotalCount int `json:"total_count"`
271271
} `json:"pagination_meta"`
272272
}
273+
274+
// CatalogType represents a catalog type in incident.io
275+
type CatalogType struct {
276+
ID string `json:"id"`
277+
Name string `json:"name"`
278+
Description string `json:"description"`
279+
TypeName string `json:"type_name"`
280+
Color string `json:"color"`
281+
Icon string `json:"icon"`
282+
Annotations map[string]interface{} `json:"annotations"`
283+
Attributes []CatalogAttribute `json:"attributes"`
284+
CreatedAt time.Time `json:"created_at"`
285+
UpdatedAt time.Time `json:"updated_at"`
286+
}
287+
288+
// CatalogAttribute represents an attribute of a catalog type
289+
type CatalogAttribute struct {
290+
ID string `json:"id"`
291+
Name string `json:"name"`
292+
Type string `json:"type"`
293+
}
294+
295+
// CatalogEntry represents a catalog entry in incident.io
296+
type CatalogEntry struct {
297+
ID string `json:"id"`
298+
Name string `json:"name"`
299+
Aliases []string `json:"aliases"`
300+
CatalogTypeID string `json:"catalog_type_id"`
301+
AttributeValues map[string]CatalogEntryAttributeValue `json:"attribute_values"`
302+
ExternalID string `json:"external_id"`
303+
Rank int `json:"rank"`
304+
CreatedAt time.Time `json:"created_at"`
305+
UpdatedAt time.Time `json:"updated_at"`
306+
}
307+
308+
// CatalogEntryAttributeValue represents an attribute value in a catalog entry
309+
type CatalogEntryAttributeValue struct {
310+
ArrayValue []CatalogEntryAttributeValueItem `json:"array_value,omitempty"`
311+
Value *CatalogEntryAttributeValueItem `json:"value,omitempty"`
312+
}
313+
314+
// CatalogEntryAttributeValueItem represents a single attribute value item
315+
type CatalogEntryAttributeValueItem struct {
316+
Literal string `json:"literal,omitempty"`
317+
ID string `json:"id,omitempty"`
318+
}
319+
320+
// ListCatalogTypesResponse represents the response from listing catalog types
321+
type ListCatalogTypesResponse struct {
322+
CatalogTypes []CatalogType `json:"catalog_types"`
323+
ListResponse
324+
}
325+
326+
// ListCatalogEntriesResponse represents the response from listing catalog entries
327+
type ListCatalogEntriesResponse struct {
328+
CatalogEntries []CatalogEntry `json:"catalog_entries"`
329+
ListResponse
330+
}
331+
332+
// UpdateCatalogEntryRequest represents a request to update a catalog entry
333+
type UpdateCatalogEntryRequest struct {
334+
Name string `json:"name,omitempty"`
335+
Aliases []string `json:"aliases,omitempty"`
336+
AttributeValues map[string]CatalogEntryAttributeValue `json:"attribute_values,omitempty"`
337+
ExternalID string `json:"external_id,omitempty"`
338+
Rank int `json:"rank,omitempty"`
339+
UpdateAttributes []string `json:"update_attributes,omitempty"`
340+
}

internal/server/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ func (s *Server) registerTools() {
110110
// Register Alert Source and Event tools
111111
s.tools["list_alert_sources"] = tools.NewListAlertSourcesTool(client)
112112
s.tools["create_alert_event"] = tools.NewCreateAlertEventTool(client)
113+
114+
// Register Catalog tools
115+
s.tools["list_catalog_types"] = tools.NewListCatalogTypesTool(client)
116+
s.tools["list_catalog_entries"] = tools.NewListCatalogEntriesTool(client)
117+
s.tools["update_catalog_entry"] = tools.NewUpdateCatalogEntryTool(client)
113118
}
114119

115120
func (s *Server) handleMessage(msg *mcp.Message) (*mcp.Message, error) {

0 commit comments

Comments
 (0)