Skip to content

Conversation

@jonathanpopham
Copy link
Collaborator

@jonathanpopham jonathanpopham commented Jan 27, 2026

Summary

Adds four new MCP tools for targeted graph generation, allowing agents to request only the graph type relevant to their task:

Tool Endpoint Use Case
get_call_graph /v1/graphs/call Trace function calls, find callers/callees
get_dependency_graph /v1/graphs/dependency Understand imports, module relationships
get_domain_graph /v1/graphs/domain High-level architecture overview
get_parse_graph /v1/graphs/parse AST-level detail for refactoring

The existing explore_codebase tool (full supermodel graph) remains available for comprehensive analysis.

Motivation

  1. Faster responses - Individual graphs are smaller and faster to generate
  2. Targeted context - Agent can request only what's relevant (e.g., call graph for "what calls this function?")
  3. Lower token cost - Smaller responses = fewer tokens in context
  4. Better agent decision-making - Agent can choose the right tool for the job

Changes

  • src/tools/graph-tools.ts - New file with factory for creating graph-type-specific tools
  • src/server.ts - Register all tools (existing + new)
  • src/types.ts - Update ClientContext to use DefaultApi directly
  • src/tools/create-supermodel-graph.ts - Update to use DefaultApi

Also Fixes

The build was broken because SupermodelClient was being imported from the SDK but doesn't exist. Updated to use DefaultApi directly.

Test Plan

  • npm run build passes
  • npm test passes (95 tests)
  • Manual testing with Claude Code

Closes #81

Summary by CodeRabbit

  • New Features

    • Added four graph-analysis tools: Call, Dependency, Domain, and Parse graphs for richer code insights.
    • Tool registry now lists and invokes all registered tools dynamically.
  • Improvements

    • More reliable uploads and idempotent processing with clearer error mapping and cleanup.
    • Dynamic tool lookup for consistent invocation behavior.
  • Tests

    • Added a local test script to exercise discovery and basic graph-tool workflows.
  • Documentation

    • Expanded README with usage guidance, tool selection help, and local development/testing instructions.

✏️ Tip: You can customize this high-level summary in your review settings.

Adds four new MCP tools for targeted graph generation:
- get_call_graph: Function-level call relationships
- get_dependency_graph: Module import relationships
- get_domain_graph: High-level domain classification
- get_parse_graph: AST-level code structure

Each tool calls its corresponding API endpoint, allowing agents to
request only the graph type relevant to their task. This provides:
- Faster responses (smaller graphs)
- Lower token costs (focused data)
- Better tool discoverability

Also fixes build issue where SupermodelClient was referenced but
doesn't exist in the SDK - now uses DefaultApi directly.

Closes #81
@coderabbitai
Copy link

coderabbitai bot commented Jan 27, 2026

Walkthrough

Adds four new graph tools (call, dependency, domain, parse) in src/tools/graph-tools.ts, registers them in src/server.ts via an aggregated allTools and toolMap, and updates ListTools and CallTool flows to use the dynamic registry for dispatch and listing.

Changes

Cohort / File(s) Summary
Server tool registration
src/server.ts
Register graphTools alongside the existing supermodel tool via allTools; build toolMap for O(1) lookup; ListTools now returns all registered tools; CallTool dispatch uses toolMap lookup and invokes the matching tool handler.
Individual graph tool implementations
src/tools/graph-tools.ts
New module exporting callGraphTool, dependencyGraphTool, domainGraphTool, parseGraphTool, and graphTools array. Implements idempotency key generation, repo ZIP creation/cleanup, reading ZIP as Blob, calling the corresponding /v1/graphs/:type API, optional jq_filter post-processing, and structured API error classification.
Docs: usage & local testing
README.md
Adds an “Individual Graph Tools” section describing tool purposes, params (directory, jq_filter), output sizing guidance, and duplicated “Local Development & Testing” instructions for running and testing locally.
Local test helper
scripts/test-local.js
New script to start the MCP server and exercise JSON-RPC flows (initialize, listTools, health ping, optional graph-tool tests: get_call_graph, get_dependency_graph, get_domain_graph, get_parse_graph) with basic sequencing and stdout/stdin handling.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Server as MCP Server
    participant ToolH as Graph Tool Handler
    participant Repo as Repository
    participant ZIP as ZIP Ops
    participant API as Graphs API

    Client->>Server: CallTool(toolName, params)
    Server->>Server: Lookup tool in toolMap
    Server->>ToolH: Invoke handler(params)
    ToolH->>Repo: Read files from path / workdir
    ToolH->>ZIP: Create ZIP archive
    ToolH->>ZIP: Read ZIP as Blob
    ToolH->>API: POST /v1/graphs/:type (Blob + idempotencyKey)
    API-->>ToolH: Graph response
    ToolH->>ToolH: Apply jq_filter (optional)
    ToolH-->>Server: Return wrapped text result
    Server-->>Client: Return tool response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • robotson

Poem

Zip it up, call the API, pick the right tree,
Call, dependency, domain, parse — small and speedy.
Tools line up, a lookup, handler springs to life,
Focused graphs for clearer dev‑sight. 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: exposing individual graph types as separate MCP tools, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR implements all four required graph tools (get_call_graph, get_dependency_graph, get_domain_graph, get_parse_graph) with proper endpoints, parameter handling, jq_filter support, and idempotency as specified in issue #81.
Out of Scope Changes check ✅ Passed All changes are directly related to exposing individual graph tools: new graph-tools.ts factory, updated server.ts registration, documentation additions, and local testing script.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/tools/graph-tools.ts`:
- Around line 255-279: The handler currently treats non-string directory values
(e.g., directory: 123) as missing; update the validation in the handler function
to explicitly check the type of providedDirectory before falling back to
defaultWorkdir and return an asErrorResult with type 'validation_error' (and a
clear message like "Invalid 'directory' parameter: expected a string") when
providedDirectory exists but is not a string; ensure you reference the existing
variables providedDirectory, directory, args and reuse the existing error shape
(code 'MISSING_DIRECTORY' or a new code like 'INVALID_DIRECTORY') so callers get
a direct, descriptive validation error instead of "missing directory."
🧹 Nitpick comments (1)
src/tools/graph-tools.ts (1)

106-216: Consider extracting shared graph helper utilities to avoid drift

generateIdempotencyKey, formatBytes, and classifyApiError largely mirror logic in src/tools/create-supermodel-graph.ts. A small shared helper module would keep behavior consistent and reduce future bugs from mismatched changes.

@jonathanpopham jonathanpopham marked this pull request as draft January 28, 2026 18:56
@jonathanpopham
Copy link
Collaborator Author

Root Cause Analysis: CI Build Failure

The Error

src/tools/create-supermodel-graph.ts(794,30): error TS2339: Property 'summary' does not exist on type 'SupermodelIRAsync'.

SDK Architecture

The SDK has two layers:

  1. DefaultApi (generated) - Raw API client

    • Returns async job wrappers: SupermodelIRAsync, CodeGraphEnvelopeAsync, etc.
    • Job wrapper: {status, jobId, retryAfter, error, result?: ActualData}
    • Caller must poll until status === 'completed'
  2. SupermodelClient (in async.js) - Polling wrapper

    • Wraps DefaultApi, handles polling automatically
    • Returns unwrapped results directly: SupermodelIR, CodeGraphEnvelope, etc.

What Went Wrong

This PR switched from SupermodelClientDefaultApi:

// Main uses SupermodelClient (returns SupermodelIR directly)
const response = await client.graphs.generateSupermodelGraph(file);
response.summary  // ✓ Works

// PR uses DefaultApi (returns SupermodelIRAsync wrapper)
const response = await client.api.generateSupermodelGraph({...});
response.summary  // ✗ Fails - data is at response.result?.summary

Why Local Typecheck Passed

Local node_modules/@supermodeltools/sdk was corrupted - missing async.js, async.d.ts, and SupermodelIRAsync.d.ts. TypeScript saw wrong types.

Fix

Keep using SupermodelClient which handles polling and returns unwrapped results. Will update the PR to use client.graphs (SupermodelClient) instead of client.api (DefaultApi).

DefaultApi returns async job wrappers (SupermodelIRAsync) that require
manual polling. SupermodelClient handles polling automatically and
returns unwrapped results with .summary property.

Fixes CI build failure: Property 'summary' does not exist on type 'SupermodelIRAsync'
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/tools/graph-tools.ts`:
- Around line 336-339: The finally block currently awaits cleanup() directly
which can throw and mask a successful response; wrap the cleanup() call in its
own try/catch inside the finally so any error is caught and logged (e.g.,
console.error or the module's logger) and not rethrown—replace "await
cleanup();" with a try { await cleanup(); } catch (err) { /* log err only */ }
to ensure cleanup failures don't propagate.

Add explicit type check before falling back to defaultWorkdir.
Returns INVALID_DIRECTORY error for values like `directory: 123`
instead of confusing "missing directory" message.
Wrap cleanup() in try/catch inside finally block. Logs warning
instead of throwing, so a successful graph generation isn't
masked by a temp file cleanup error.
- Document get_call_graph, get_dependency_graph, get_domain_graph, get_parse_graph
- Add "Choosing the Right Tool" comparison table
- Add Local Development & Testing section with build/test instructions
- Include test-local.js script for verifying server functionality
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/tools/graph-tools.ts`:
- Around line 264-277: The code currently validates providedDirectory but lacks
a type guard for jq_filter; add a check after the args destructuring to ensure
jq_filter is either undefined or a string and if not return asErrorResult with
type 'validation_error' (similar shape to the directory error) so invalid inputs
(e.g., jq_filter: 123) produce a clear validation error before calling
maybeFilter; reference the jq_filter variable, the maybeFilter call, and the
asErrorResult helper when implementing this guard.
- Around line 327-330: The code constructs a Blob (new Blob([...])) in the try
block around readFile(zipPath), which is only available globally in Node 18+;
either enforce Node 18+ by adding an "engines": { "node": ">=18" } entry to
package.json, or (preferred for older Node support) import Blob from the buffer
module and use that: add import { Blob } from 'buffer' at the top of the module
so the new Blob([...]) call in src/tools/graph-tools.ts works on Node versions
<18; update tests/CI if you change the engine requirement.

Comment on lines +264 to +277
const { jq_filter, directory: providedDirectory } = args as {
jq_filter?: string;
directory?: string;
};

if (providedDirectory !== undefined && typeof providedDirectory !== 'string') {
return asErrorResult({
type: 'validation_error',
message: 'Invalid "directory" parameter. Provide a valid directory path as a string.',
code: 'INVALID_DIRECTORY',
recoverable: false,
suggestion: 'Pass directory as a string path, e.g. directory="/workspace/my-repo".',
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a type check for jq_filter to avoid confusing internal errors.

Right now, jq_filter: 123 would likely blow up inside maybeFilter and return an internal error. A quick guard gives a clean validation error.

♻️ Suggested patch
   const { jq_filter, directory: providedDirectory } = args as {
     jq_filter?: string;
     directory?: string;
   };
 
+  if (jq_filter !== undefined && typeof jq_filter !== 'string') {
+    return asErrorResult({
+      type: 'validation_error',
+      message: 'Invalid "jq_filter" parameter. Provide a jq filter string.',
+      code: 'INVALID_JQ_FILTER',
+      recoverable: false,
+      suggestion: 'Pass jq_filter as a string, e.g. jq_filter=".nodes".',
+    });
+  }
+
   if (providedDirectory !== undefined && typeof providedDirectory !== 'string') {
     return asErrorResult({
       type: 'validation_error',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { jq_filter, directory: providedDirectory } = args as {
jq_filter?: string;
directory?: string;
};
if (providedDirectory !== undefined && typeof providedDirectory !== 'string') {
return asErrorResult({
type: 'validation_error',
message: 'Invalid "directory" parameter. Provide a valid directory path as a string.',
code: 'INVALID_DIRECTORY',
recoverable: false,
suggestion: 'Pass directory as a string path, e.g. directory="/workspace/my-repo".',
});
}
const { jq_filter, directory: providedDirectory } = args as {
jq_filter?: string;
directory?: string;
};
if (jq_filter !== undefined && typeof jq_filter !== 'string') {
return asErrorResult({
type: 'validation_error',
message: 'Invalid "jq_filter" parameter. Provide a jq filter string.',
code: 'INVALID_JQ_FILTER',
recoverable: false,
suggestion: 'Pass jq_filter as a string, e.g. jq_filter=".nodes".',
});
}
if (providedDirectory !== undefined && typeof providedDirectory !== 'string') {
return asErrorResult({
type: 'validation_error',
message: 'Invalid "directory" parameter. Provide a valid directory path as a string.',
code: 'INVALID_DIRECTORY',
recoverable: false,
suggestion: 'Pass directory as a string path, e.g. directory="/workspace/my-repo".',
});
}
🤖 Prompt for AI Agents
In `@src/tools/graph-tools.ts` around lines 264 - 277, The code currently
validates providedDirectory but lacks a type guard for jq_filter; add a check
after the args destructuring to ensure jq_filter is either undefined or a string
and if not return asErrorResult with type 'validation_error' (similar shape to
the directory error) so invalid inputs (e.g., jq_filter: 123) produce a clear
validation error before calling maybeFilter; reference the jq_filter variable,
the maybeFilter call, and the asErrorResult helper when implementing this guard.

Comment on lines +327 to +330
try {
const fileBuffer = await readFile(zipPath);
const fileBlob = new Blob([fileBuffer], { type: 'application/zip' });

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine package.json files for Node engine constraints
echo "=== Searching for package.json files ==="
find . -name "package.json" -type f | head -20

echo ""
echo "=== Checking Node engine constraints ==="
find . -name "package.json" -type f -exec grep -H -A 3 '"engines"' {} \;

Repository: supermodeltools/mcp

Length of output: 160


🏁 Script executed:

#!/bin/bash
# Read the package.json to check for engines field
echo "=== Full package.json ==="
cat ./package.json | head -50

Repository: supermodeltools/mcp

Length of output: 1294


🏁 Script executed:

#!/bin/bash
# Check the file at the specified lines and surrounding context
echo "=== Viewing src/tools/graph-tools.ts around lines 327-330 ==="
sed -n '320,340p' src/tools/graph-tools.ts

echo ""
echo "=== Check all imports at the top of the file ==="
head -30 src/tools/graph-tools.ts

echo ""
echo "=== Search for Blob usage in the entire file ==="
grep -n "Blob\|undici" src/tools/graph-tools.ts

Repository: supermodeltools/mcp

Length of output: 2019


Add explicit Node version constraint or import Blob from buffer.

Your code uses new Blob(...) which only exists as a global in Node 18+. Since your package.json doesn't specify engines.node, this code could theoretically run on Node 16/14 and crash at runtime with "Blob is not defined."

Pick one fix:

  1. Enforce Node 18+ in package.json — add "engines": { "node": ">=18" } if you're okay requiring Node 18+
  2. Import Blob explicitly — safer if you need to support older versions. Just add import { Blob } from 'buffer'; since you already have undici available
Option 2 — Import Blob
import { readFile } from 'fs/promises';
+import { Blob } from 'buffer';
🤖 Prompt for AI Agents
In `@src/tools/graph-tools.ts` around lines 327 - 330, The code constructs a Blob
(new Blob([...])) in the try block around readFile(zipPath), which is only
available globally in Node 18+; either enforce Node 18+ by adding an "engines":
{ "node": ">=18" } entry to package.json, or (preferred for older Node support)
import Blob from the buffer module and use that: add import { Blob } from
'buffer' at the top of the module so the new Blob([...]) call in
src/tools/graph-tools.ts works on Node versions <18; update tests/CI if you
change the engine requirement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose individual graph types as separate MCP tools

2 participants