Skip to content

Commit b7bb183

Browse files
p25martiwesleyjellisclaudeFarjaad
authored
Add Service Dependency Support (#99)
* Add service dependency support * Improve service dependency tools with validation and cleaner output - Validate service exists via GetService before fetching dependencies/dependents - Rename ServiceId field to ComponentId for clarity - Remove non-functional search parameter from both tools - Remove Locked field from serialized output - Use empty slice instead of nil for consistent JSON output Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add changie entry for service dependency tools Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Apply suggestions from code review Co-authored-by: Farjaad <8631006+Farjaad@users.noreply.github.com> * rename all service references in new field to component --------- Co-authored-by: Wesley Ellis <wesley@opslevel.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Farjaad <8631006+Farjaad@users.noreply.github.com>
1 parent 7b731d6 commit b7bb183

3 files changed

Lines changed: 116 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
kind: Added
2+
body: Add componentDependencies and componentDependents tools to fetch the upstream and downstream service dependency graph for a given component
3+
time: 2026-02-18T09:27:48.836718-05:00

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Currently, the MCP server only uses read-only access to your OpsLevel account an
3838
- Campaigns
3939
- Checks
4040
- Components
41+
- Component Dependencies (components that a component depends on)
42+
- Component Dependents (components that depend on a component)
4143
- Documentation (API & Tech Docs)
4244
- Domains
4345
- Filters

src/cmd/root.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ type serializedCampaign struct {
107107
Reminder *opslevel.CampaignReminder
108108
}
109109

110+
type serializedDependency struct {
111+
Id string
112+
ComponentId string
113+
Aliases []string
114+
Notes string
115+
}
116+
110117
// AccountMetadata represents the different types of account metadata that can be fetched
111118
type AccountMetadata string
112119

@@ -768,6 +775,110 @@ For complete reference:
768775
return newToolResult(campaigns, err)
769776
})
770777

778+
// Register component dependencies tool
779+
s.AddTool(
780+
mcp.NewTool(
781+
"componentDependencies",
782+
mcp.WithDescription("Get all the components that a specific component depends on. Returns the dependency graph showing which components this component consumes or calls."),
783+
mcp.WithString("componentId", mcp.Required(), mcp.Description("The id of the component to fetch dependencies for.")),
784+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
785+
Title: "Component Dependencies in OpsLevel",
786+
ReadOnlyHint: &trueValue,
787+
DestructiveHint: &falseValue,
788+
IdempotentHint: &trueValue,
789+
OpenWorldHint: &trueValue,
790+
}),
791+
),
792+
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
793+
componentId, err := req.RequireString("componentId")
794+
if err != nil {
795+
return mcp.NewToolResultError("componentId parameter is required"), nil
796+
}
797+
798+
service, err := client.GetService(componentId)
799+
if err != nil {
800+
return mcp.NewToolResultErrorFromErr("failed to get component", err), nil
801+
}
802+
if service.Id == "" {
803+
return mcp.NewToolResultError(fmt.Sprintf("component with id %s not found", componentId)), nil
804+
}
805+
806+
variables := &opslevel.PayloadVariables{
807+
"after": "",
808+
"first": 100,
809+
}
810+
811+
resp, err := service.GetDependencies(client, variables)
812+
if err != nil {
813+
return mcp.NewToolResultErrorFromErr("failed to get dependencies", err), nil
814+
}
815+
816+
dependencies := []serializedDependency{}
817+
for _, edge := range resp.Edges {
818+
dep := serializedDependency{
819+
Id: string(edge.Id),
820+
ComponentId: string(edge.Node.Id),
821+
Aliases: edge.Node.Aliases,
822+
Notes: edge.Notes,
823+
}
824+
dependencies = append(dependencies, dep)
825+
}
826+
827+
return newToolResult(dependencies, nil)
828+
})
829+
830+
// Register component dependents tool
831+
s.AddTool(
832+
mcp.NewTool(
833+
"componentDependents",
834+
mcp.WithDescription("Get all the components that depend on a specific component. Returns the reverse dependency graph showing which components consume or call this component."),
835+
mcp.WithString("componentId", mcp.Required(), mcp.Description("The id of the component to fetch dependents for.")),
836+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
837+
Title: "Component Dependents in OpsLevel",
838+
ReadOnlyHint: &trueValue,
839+
DestructiveHint: &falseValue,
840+
IdempotentHint: &trueValue,
841+
OpenWorldHint: &trueValue,
842+
}),
843+
),
844+
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
845+
componentId, err := req.RequireString("componentId")
846+
if err != nil {
847+
return mcp.NewToolResultError("componentId parameter is required"), nil
848+
}
849+
850+
service, err := client.GetService(componentId)
851+
if err != nil {
852+
return mcp.NewToolResultErrorFromErr("failed to get component", err), nil
853+
}
854+
if service.Id == "" {
855+
return mcp.NewToolResultError(fmt.Sprintf("component with id %s not found", componentId)), nil
856+
}
857+
858+
variables := &opslevel.PayloadVariables{
859+
"after": "",
860+
"first": 100,
861+
}
862+
863+
resp, err := service.GetDependents(client, variables)
864+
if err != nil {
865+
return mcp.NewToolResultErrorFromErr("failed to get dependents", err), nil
866+
}
867+
868+
dependents := []serializedDependency{}
869+
for _, edge := range resp.Edges {
870+
dep := serializedDependency{
871+
Id: string(edge.Id),
872+
ComponentId: string(edge.Node.Id),
873+
Aliases: edge.Node.Aliases,
874+
Notes: edge.Notes,
875+
}
876+
dependents = append(dependents, dep)
877+
}
878+
879+
return newToolResult(dependents, nil)
880+
})
881+
771882
log.Info().Msg("Starting MCP server...")
772883
if err := server.ServeStdio(s); err != nil {
773884
if err == context.Canceled {

0 commit comments

Comments
 (0)