Skip to content

Commit d4fc60c

Browse files
docs(mcp): annotate graph and workspace tools
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 61e0f8b commit d4fc60c

13 files changed

Lines changed: 82 additions & 45 deletions

internal/mcp/handler_graph.go

Lines changed: 34 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,24 @@ import (
1212
"github.com/tae2089/code-context-graph/internal/model"
1313
)
1414

15+
// graphFlowInfo represents a summarized flow response entry.
16+
// @intent serialize listFlows results with the legacy response shape.
17+
type graphFlowInfo struct {
18+
ID uint `json:"id"`
19+
Name string `json:"name"`
20+
Description string `json:"description"`
21+
NodeCount int `json:"node_count"`
22+
}
23+
24+
// graphCommInfo represents a summarized community response entry.
25+
// @intent serialize listCommunities results with the legacy response shape.
26+
type graphCommInfo struct {
27+
ID uint `json:"id"`
28+
Label string `json:"label"`
29+
NodeCount int `json:"node_count"`
30+
Cohesion float64 `json:"cohesion"`
31+
}
32+
1533
// listFlows lists stored flows with optional sorting and truncation.
1634
// @intent 저장된 호출 흐름을 요약 형태로 노출해 탐색과 우선순위 판단을 돕는다.
1735
// @param request sort_by와 limit로 정렬 방식과 최대 개수를 제어한다.
@@ -29,13 +47,6 @@ func (h *handlers) listFlows(ctx context.Context, request mcp.CallToolRequest) (
2947

3048
log.Info("list_flows called", "sort_by", sortBy, "limit", limit)
3149

32-
// flowCount stores aggregated membership counts per flow.
33-
// @intent flow_id별 멤버 수 집계 결과를 스캔하기 위한 임시 구조체다.
34-
type flowCount struct {
35-
FlowID uint
36-
Count int
37-
}
38-
3950
return finalizeToolResult(h.cachedExecute(ctx, "list_flows:", map[string]any{"sort_by": sortBy, "limit": limit, "namespace": requestNamespace(request)}, func() (string, error) {
4051
ns := ctxns.FromContext(ctx)
4152
var fcRows []flowCount
@@ -60,18 +71,9 @@ func (h *handlers) listFlows(ctx context.Context, request mcp.CallToolRequest) (
6071
return "", trace.Wrap(err, "find flows")
6172
}
6273

63-
// flowInfo represents a summarized flow response entry.
64-
// @intent MCP 응답에서 각 flow의 핵심 정보만 직렬화한다.
65-
type flowInfo struct {
66-
ID uint `json:"id"`
67-
Name string `json:"name"`
68-
Description string `json:"description"`
69-
NodeCount int `json:"node_count"`
70-
}
71-
72-
infos := make([]flowInfo, len(flowList))
74+
infos := make([]graphFlowInfo, len(flowList))
7375
for i, f := range flowList {
74-
infos[i] = flowInfo{
76+
infos[i] = graphFlowInfo{
7577
ID: f.ID,
7678
Name: f.Name,
7779
Description: f.Description,
@@ -119,13 +121,6 @@ func (h *handlers) listCommunities(ctx context.Context, request mcp.CallToolRequ
119121

120122
log.Info("list_communities called", "sort_by", sortBy, "min_size", minSize)
121123

122-
// commCount stores aggregated membership counts per community.
123-
// @intent community_id별 멤버 수 집계 결과를 스캔하기 위한 임시 구조체다.
124-
type commCount struct {
125-
CommunityID uint
126-
Count int
127-
}
128-
129124
return finalizeToolResult(h.cachedExecute(ctx, "list_communities:", map[string]any{"sort_by": sortBy, "min_size": minSize, "namespace": requestNamespace(request)}, func() (string, error) {
130125
ns := ctxns.FromContext(ctx)
131126
var ccRows []commCount
@@ -151,22 +146,13 @@ func (h *handlers) listCommunities(ctx context.Context, request mcp.CallToolRequ
151146
return "", trace.Wrap(err, "find communities")
152147
}
153148

154-
// commInfo represents a summarized community response entry.
155-
// @intent MCP 응답에서 커뮤니티의 핵심 메타데이터만 직렬화한다.
156-
type commInfo struct {
157-
ID uint `json:"id"`
158-
Label string `json:"label"`
159-
NodeCount int `json:"node_count"`
160-
Cohesion float64 `json:"cohesion"`
161-
}
162-
163-
infos := make([]commInfo, 0, len(communities))
149+
infos := make([]graphCommInfo, 0, len(communities))
164150
for _, c := range communities {
165151
cnt := ccMap[c.ID]
166152
if cnt < minSize {
167153
continue
168154
}
169-
infos = append(infos, commInfo{
155+
infos = append(infos, graphCommInfo{
170156
ID: c.ID,
171157
Label: c.Label,
172158
NodeCount: cnt,
@@ -228,9 +214,9 @@ func (h *handlers) getCommunity(ctx context.Context, request mcp.CallToolRequest
228214
}
229215

230216
gcData := map[string]any{
231-
"id": comm.ID,
232-
"label": comm.Label,
233-
"node_count": memberCount,
217+
"id": comm.ID,
218+
"label": comm.Label,
219+
"node_count": memberCount,
234220
"derived_state": derivedStateCommunities(),
235221
}
236222

@@ -299,9 +285,9 @@ func (h *handlers) getArchitectureOverview(ctx context.Context, request mcp.Call
299285

300286
if len(communities) == 0 {
301287
result, err := marshalJSON(map[string]any{
302-
"communities": []any{},
303-
"coupling": []any{},
304-
"warnings": []string{"No communities found. Run community rebuild first."},
288+
"communities": []any{},
289+
"coupling": []any{},
290+
"warnings": []string{"No communities found. Run community rebuild first."},
305291
"derived_state": derivedStateSummary(),
306292
})
307293
if err != nil {
@@ -363,9 +349,9 @@ func (h *handlers) getArchitectureOverview(ctx context.Context, request mcp.Call
363349
}
364350

365351
result, err := marshalJSON(map[string]any{
366-
"communities": commInfos,
367-
"coupling": couplingPairs,
368-
"warnings": warnings,
352+
"communities": commInfos,
353+
"coupling": couplingPairs,
354+
"warnings": warnings,
369355
"derived_state": derivedStateSummary(),
370356
})
371357
if err != nil {
@@ -375,6 +361,7 @@ func (h *handlers) getArchitectureOverview(ctx context.Context, request mcp.Call
375361
}))
376362
}
377363

364+
// @intent describe community-membership freshness so callers know when to re-run postprocess.
378365
func derivedStateCommunities() map[string]any {
379366
return map[string]any{
380367
"communities": map[string]any{
@@ -385,6 +372,7 @@ func derivedStateCommunities() map[string]any {
385372
}
386373
}
387374

375+
// @intent describe flow-membership freshness so callers know when to re-run postprocess.
388376
func derivedStateFlows() map[string]any {
389377
return map[string]any{
390378
"flows": map[string]any{
@@ -395,6 +383,7 @@ func derivedStateFlows() map[string]any {
395383
}
396384
}
397385

386+
// @intent merge community and flow freshness hints into a single derived-state map for status responses.
398387
func derivedStateSummary() map[string]any {
399388
state := derivedStateCommunities()
400389
for k, v := range derivedStateFlows() {

internal/mcp/handler_workspace.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func (h *handlers) workspaceRoot() string {
3737
return root
3838
}
3939

40+
// @intent resolve the canonical namespace parameter while preserving the deprecated workspace alias.
4041
func requestWorkspace(request mcp.CallToolRequest) (string, error) {
4142
if namespace := request.GetString("namespace", ""); namespace != "" {
4243
return namespace, nil
@@ -67,6 +68,8 @@ func validateWorkspacePath(workspace, filePath string) error {
6768
return nil
6869
}
6970

71+
// @intent canonicalize and create the workspace root before file operations rely on it.
72+
// @sideEffect creates the workspace root directory when it does not yet exist.
7073
func (h *handlers) safeWorkspaceRoot() (string, error) {
7174
root := h.workspaceRoot()
7275
absRoot, err := filepath.Abs(root)
@@ -83,11 +86,13 @@ func (h *handlers) safeWorkspaceRoot() (string, error) {
8386
return realRoot, nil
8487
}
8588

89+
// @intent compose filesystem paths without repeating join boilerplate in workspace helpers.
8690
func safeJoin(base string, parts ...string) string {
8791
all := append([]string{base}, parts...)
8892
return filepath.Join(all...)
8993
}
9094

95+
// @intent reject symlink traversal anywhere along a workspace path before file operations touch the filesystem.
9196
func ensureNoSymlinkInPath(root, relPath string, allowMissingLeaf bool) (string, error) {
9297
cleanRel := filepath.Clean(relPath)
9398
if cleanRel == "." {
@@ -114,6 +119,7 @@ func ensureNoSymlinkInPath(root, relPath string, allowMissingLeaf bool) (string,
114119
return current, nil
115120
}
116121

122+
// @intent resolve a workspace-relative path under the trusted root after validation and symlink checks.
117123
func (h *handlers) resolveWorkspacePath(workspace, filePath string, allowMissingLeaf bool) (string, error) {
118124
if err := validateWorkspacePath(workspace, filePath); err != nil {
119125
return "", err
@@ -137,6 +143,8 @@ func (h *handlers) resolveWorkspacePath(workspace, filePath string, allowMissing
137143
return ensureNoSymlinkInPath(root, rel, allowMissingLeaf)
138144
}
139145

146+
// @intent write workspace files atomically so partial writes are never observed as final state.
147+
// @sideEffect creates a temp file and renames it into place.
140148
func safeWriteFile(path string, data []byte, perm os.FileMode) error {
141149
tmpFile, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)+".tmp.*")
142150
if err != nil {
@@ -356,12 +364,15 @@ type uploadFileEntry struct {
356364
Content string `json:"content"`
357365
}
358366

367+
// @intent carry decoded content and the resolved target path through the workspace upload pipeline.
359368
type preparedUploadFile struct {
360369
entry uploadFileEntry
361370
decoded []byte
362371
target string
363372
}
364373

374+
// @intent hold validated upload payload state so bulk writes can happen after all entries pass validation.
375+
365376
// uploadFiles writes multiple base64-encoded files in one request.
366377
// @intent 여러 작업공간 파일을 한 번의 MCP 호출로 업로드해 왕복 비용을 줄인다.
367378
// @param request files는 uploadFileEntry 배열을 담은 JSON 문자열이다.

internal/mcp/handler_workspace_unix.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
//go:build !windows
22

3+
// @index Unix-specific symlink-safe workspace file writes for MCP handlers.
34
package mcp
45

56
import (
67
"os"
78
"syscall"
89
)
910

11+
// writeFileNoFollow writes workspace files without following symlinks on Unix.
12+
// @intent prevent workspace upload paths from escaping the allowed root through symlink traversal.
13+
// @sideEffect creates or truncates the target file and fsyncs it to disk.
1014
func writeFileNoFollow(path string, data []byte, perm os.FileMode) error {
1115
fd, err := syscall.Open(path, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC|syscall.O_NOFOLLOW, uint32(perm))
1216
if err != nil {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
//go:build windows
22

3+
// @index Windows workspace file writes for MCP handlers.
34
package mcp
45

56
import "os"
67

8+
// writeFileNoFollow writes workspace files using the Windows-safe fallback path.
9+
// @intent provide the same workspace write entry point on Windows where O_NOFOLLOW is unavailable.
10+
// @sideEffect truncates or creates the target file.
711
func writeFileNoFollow(path string, data []byte, perm os.FileMode) error {
812
return os.WriteFile(path, data, perm)
913
}

internal/mcp/tools_analysis.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
// @index MCP tool registration for analysis-oriented graph operations.
12
package mcp
23

34
import (
45
"github.com/mark3labs/mcp-go/mcp"
56
"github.com/mark3labs/mcp-go/server"
67
)
78

9+
// analysisTools registers tools that compute impact, flow, change, and dead-code insights.
10+
// @intent keep analysis capabilities grouped so server startup can expose them consistently.
811
func analysisTools(h *handlers) []server.ServerTool {
912
return []server.ServerTool{
1013
{

internal/mcp/tools_context.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
// @index MCP tool registration for lightweight context discovery.
12
package mcp
23

34
import (
45
"github.com/mark3labs/mcp-go/mcp"
56
"github.com/mark3labs/mcp-go/server"
67
)
78

9+
// contextTools registers compact discovery tools that help callers choose deeper graph operations.
10+
// @intent keep the context-oriented MCP surface grouped and reusable during server startup.
811
func contextTools(h *handlers) []server.ServerTool {
912
return []server.ServerTool{
1013
{

internal/mcp/tools_docs.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
// @index MCP tool registration for documentation and RAG index operations.
12
package mcp
23

34
import (
45
"github.com/mark3labs/mcp-go/mcp"
56
"github.com/mark3labs/mcp-go/server"
67
)
78

9+
// docsTools registers tools that build and query the documentation index.
10+
// @intent keep documentation retrieval flows discoverable as one MCP tool family.
811
func docsTools(h *handlers) []server.ServerTool {
912
return []server.ServerTool{
1013
{

internal/mcp/tools_graph.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
// @index MCP tool registration for graph summaries and architecture views.
12
package mcp
23

34
import (
45
"github.com/mark3labs/mcp-go/mcp"
56
"github.com/mark3labs/mcp-go/server"
67
)
78

9+
// graphTools registers tools that summarize stored flows, communities, and architecture state.
10+
// @intent expose high-level graph inspection separately from low-level query primitives.
811
func graphTools(h *handlers) []server.ServerTool {
912
return []server.ServerTool{
1013
{

internal/mcp/tools_parse.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
// @index MCP tool registration for parsing, graph build, and postprocess execution.
12
package mcp
23

34
import (
45
"github.com/mark3labs/mcp-go/mcp"
56
"github.com/mark3labs/mcp-go/server"
67
)
78

9+
// parseTools registers tools that ingest source code and rebuild derived graph state.
10+
// @intent keep parsing and postprocess entry points available as one operational tool family.
811
func parseTools(h *handlers) []server.ServerTool {
912
return []server.ServerTool{
1013
{

internal/mcp/tools_postprocess.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
// @index MCP tool registration for automatic postprocess policy control.
12
package mcp
23

34
import (
45
"github.com/mark3labs/mcp-go/mcp"
56
"github.com/mark3labs/mcp-go/server"
67
)
78

9+
// postprocessTools registers policy inspection and reset tools for postprocess automation.
10+
// @intent make postprocess recovery controls available without mixing them into unrelated tool groups.
811
func postprocessTools(h *handlers) []server.ServerTool {
912
return []server.ServerTool{
1013
{

0 commit comments

Comments
 (0)