@morphllm/gitmorph-sdk
v0.3.1
Published
Agent-native TypeScript SDK for reading Git repositories. Drop-in tools for the Vercel AI SDK, Anthropic, OpenAI, and MCP.
Readme
@morphllm/gitmorph-sdk
Agent-native TypeScript SDK for reading, searching, and navigating Git repositories through GitMorph. No cloning. Every operation is a single HTTP call against a server-side mirror.
Built for AI coding agents that need to understand codebases: read files with line ranges, grep across repos, glob for file patterns, list directories, walk commits. All server-side, all fast.
npm install @morphllm/gitmorph-sdkQuick start
import { GitMorph } from "@morphllm/gitmorph-sdk";
const gm = new GitMorph();
const repo = gm.repo("facebook/react");
// Read a file
const file = await repo.readFile("package.json");
// Grep for a pattern
const results = await repo.grep({ pattern: "useState" });
// Glob for files
const tsFiles = await repo.glob({ patterns: ["**/*.ts"] });Give your agent access to any repo
Paste this into Claude Code (or any agent with tool-use) to give it read access to any mirrored repository:
You have access to the @morphllm/gitmorph-sdk for reading Git repositories without cloning.
The SDK is already installed and authenticated via ~/.config/gm/config.json.
To use it, write a quick TypeScript script and run it with `npx tsx`:
```ts
import { GitMorph } from "@morphllm/gitmorph-sdk";
const gm = new GitMorph();
const repo = gm.repo("owner/repo");
```
Available methods on `repo`:
- `repo.readFile(path, { ref?, lines?: [{ start, end }] })` - read a file, optionally specific line ranges (1-indexed, inclusive)
- `repo.getFileContents(paths[], { ref? })` - batch read multiple files in one call
- `repo.grep({ pattern, language?, caseSensitive?, maxMatches?, page? })` - server-side code search within the repo
- `repo.glob({ patterns[], ref?, prefix?, sizes?, limit? })` - find files matching glob patterns (e.g. `["**/*.ts", "!**/*.test.ts"]`)
- `repo.listDir({ path?, ref?, recursive? })` - list directory contents
- `repo.listBranches({ page?, limit? })` - list branches
- `repo.listCommits({ sha?, path?, since?, until?, page?, limit? })` - list commits
Available methods on `gm` (instance-wide):
- `gm.grepAll({ pattern, language?, page?, limit?, sortByStars? })` - search code across ALL repos
- `gm.mirror(source, options?)` - mirror a GitHub/GitLab/Gitea repo (e.g. `gm.mirror("facebook/react")`)
- `gm.getRepositoryInfo("owner/repo")` - get repo metadata
Use these to explore, search, and read code. Prefer grep and glob over listing directories manually.Drop-in LLM tools
The SDK ships framework-native tool adapters under subpath exports. Import, pass to your agent, go. No hand-written schemas, no dispatcher wiring.
Every adapter exposes the same factories:
createGitmorphTools(gm, opts?)— multi-repo; every tool takesrepo: "owner/name".createGitmorphRepoTools(gm, "owner/name", opts?)— pinned to one repo; tools drop therepoarg.
Each adapter is an optional peer dependency — you only install what you use.
Vercel AI SDK
npm install ai zodimport { GitMorph } from "@morphllm/gitmorph-sdk";
import { createGitmorphRepoTools, GITMORPH_SYSTEM_PROMPT } from "@morphllm/gitmorph-sdk/ai";
import { anthropic } from "@ai-sdk/anthropic";
import { generateText } from "ai";
const gm = new GitMorph();
const { text } = await generateText({
model: anthropic("claude-sonnet-4-6"),
system: GITMORPH_SYSTEM_PROMPT,
tools: createGitmorphRepoTools(gm, "facebook/react"),
prompt: "What testing framework does this repo use? Read package.json to confirm.",
maxSteps: 5,
});Pass only as a const tuple to narrow the return type — autocomplete shows exactly the tools you asked for:
const tools = createGitmorphTools(gm, { only: ["gm_grep", "gm_read_file"] as const });
// tools has exactly { gm_grep: Tool; gm_read_file: Tool }Anthropic SDK
npm install @anthropic-ai/sdk zod zod-to-json-schemaimport Anthropic from "@anthropic-ai/sdk";
import { GitMorph } from "@morphllm/gitmorph-sdk";
import { createGitmorphTools, GITMORPH_SYSTEM_PROMPT } from "@morphllm/gitmorph-sdk/anthropic";
const anthropic = new Anthropic();
const gm = new GitMorph();
const { tools, handleToolUse } = createGitmorphTools(gm, { cacheControl: true });
const messages: Anthropic.Messages.MessageParam[] = [
{ role: "user", content: "Find every useEffect in facebook/react" },
];
let msg = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 4096,
system: GITMORPH_SYSTEM_PROMPT,
tools,
messages,
});
while (msg.stop_reason === "tool_use") {
const uses = msg.content.filter((b): b is Anthropic.Messages.ToolUseBlock => b.type === "tool_use");
const results = await Promise.all(uses.map(handleToolUse));
messages.push({ role: "assistant", content: msg.content });
messages.push({ role: "user", content: results });
msg = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 4096,
system: GITMORPH_SYSTEM_PROMPT,
tools,
messages,
});
}handleToolUse never throws. Errors become tool_result blocks with is_error: true so the model can recover in the next turn. cacheControl: true attaches Anthropic prompt caching to the tool list — stable tool sets become a cached prefix of every request.
OpenAI SDK
npm install openai zod zod-to-json-schemaimport OpenAI from "openai";
import { GitMorph } from "@morphllm/gitmorph-sdk";
import { createGitmorphTools, GITMORPH_SYSTEM_PROMPT } from "@morphllm/gitmorph-sdk/openai";
const openai = new OpenAI();
const gm = new GitMorph();
const { tools, handleToolCall } = createGitmorphTools(gm);
const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
{ role: "system", content: GITMORPH_SYSTEM_PROMPT },
{ role: "user", content: "Find every useEffect in facebook/react" },
];
for (let step = 0; step < 10; step++) {
const res = await openai.chat.completions.create({ model: "gpt-4o", tools, messages });
const msg = res.choices[0]!.message;
messages.push(msg);
if (!msg.tool_calls?.length) break;
const results = await Promise.all(msg.tool_calls.map(handleToolCall));
messages.push(...results);
}MCP (Claude Desktop / Cursor / Claude Code)
Zero-code path. Add GitMorph to your MCP client's config:
{
"mcpServers": {
"gitmorph": {
"command": "npx",
"args": ["-y", "@morphllm/gitmorph-sdk", "mcp"],
"env": { "GM_TOKEN": "gm_..." }
}
}
}No install. No global CLI. The bin entry in this package registers gitmorph-mcp, and npx -y runs it directly. Auth resolves from the env block, from ~/.config/gm/config.json, or from your shell environment.
To embed the MCP server in your own Node process:
import { GitMorph } from "@morphllm/gitmorph-sdk";
import { startGitmorphMcpServer } from "@morphllm/gitmorph-sdk/mcp";
await startGitmorphMcpServer(new GitMorph());Tool reference
All adapters expose the same 9 tools by default (gm_mirror is opt-in via includeMirror: true):
| Tool | Purpose |
|------|---------|
| gm_read_file | Read a file, optionally a specific line range |
| gm_read_files | Batch-read multiple files in one call |
| gm_grep | Search file contents within one repo (regex or keyword) |
| gm_glob | Find files by path pattern (**/*.ts) |
| gm_list_dir | List entries in a directory |
| gm_list_branches | List branches with head commits |
| gm_list_commits | List commits, filterable by path or date range |
| gm_grep_all | Search code across all mirrored repositories |
| gm_get_repo_info | Fetch metadata for a repository |
| gm_mirror | Opt-in. Mirror a GitHub/GitLab/Gitea repo into GitMorph |
Customizing
Every factory accepts the same options:
createGitmorphTools(gm, {
only: ["gm_grep", "gm_read_file"] as const, // whitelist (narrows the return type)
exclude: ["gm_list_branches"], // drop specific tools
rename: { gm_grep: "search_code" }, // rename to match your agent's naming scheme
includeMirror: false, // enable the opt-in gm_mirror tool
maxOutputBytes: 262_144, // cap per-call output (Anthropic/OpenAI/MCP)
});GITMORPH_SYSTEM_PROMPT is re-exported from every subpath — append it to your system message to give the model explicit guidance on when to reach for each tool.
Auth
Credentials resolve in order:
- Constructor options:
new GitMorph({ token: "...", host: "..." }) - Environment variables:
GM_TOKEN,GM_HOST - Config file:
~/.config/gm/config.json
The host defaults to gitmorph.com if not set.
API reference
GitMorph (client)
| Method | Description |
|--------|-------------|
| gm.repo("owner/repo") | Get a repo handle for file/search operations |
| gm.getRepositoryInfo("owner/repo") | Repo metadata (branches, stars, language, etc.) |
| gm.mirror(source, opts?) | Mirror a repo from GitHub/GitLab/Gitea into GitMorph |
| gm.grepAll({ pattern, ... }) | Search code across all repos on the instance |
GitMorphRepo (per-repo operations)
| Method | Description |
|--------|-------------|
| repo.readFile(path, opts?) | Read a file, optionally specific line ranges |
| repo.getFileContents(paths[], opts?) | Batch read multiple files in one call |
| repo.grep({ pattern, ... }) | Server-side code search within this repo |
| repo.glob({ patterns[], ... }) | Find files matching glob patterns |
| repo.listDir(opts?) | List directory contents (optionally recursive) |
| repo.listBranches(opts?) | List branches with latest commit info |
| repo.listCommits(opts?) | List commits with filtering by path, date range |
Read files
// Full file
const file = await repo.readFile("src/index.ts");
// file.content, file.totalLines
// Specific line ranges (1-indexed, inclusive)
const snippet = await repo.readFile("src/index.ts", {
ref: "main",
lines: [{ start: 10, end: 25 }, { start: 100, end: 110 }],
});Batch read
const files = await repo.getFileContents([
"package.json",
"tsconfig.json",
"src/index.ts",
]);
// Returns (FileContentEntry | null)[] -- null for files that don't existGrep (single repo)
const result = await repo.grep({
pattern: "createServer",
language: "TypeScript",
caseSensitive: true,
maxMatches: 20,
});
for (const match of result.matches) {
console.log(`${match.path}:${match.lineNumber} ${match.lineContent}`);
// match.submatches[] has character offsets within the line
}Grep all (cross-repo)
const result = await gm.grepAll({
pattern: "fastApply",
language: "TypeScript",
sortByStars: true,
limit: 10,
});
for (const match of result.matches) {
console.log(`${match.repoName} ${match.filename}`);
for (const line of match.lines) {
console.log(` L${line.num}: ${line.content}`);
}
}Glob
const result = await repo.glob({
patterns: ["src/**/*.ts", "!**/*.test.ts", "!**/*.spec.ts"],
prefix: "src/",
limit: 500,
});
for (const entry of result.entries) {
console.log(`${entry.path} ${entry.size} bytes`);
}List directory
// Top-level
const root = await repo.listDir();
// Subdirectory, recursive
const src = await repo.listDir({ path: "src", recursive: true, ref: "develop" });
for (const entry of src.entries) {
console.log(`${entry.type === "dir" ? "d" : "f"} ${entry.path}`);
}Branches
const branches = await repo.listBranches({ limit: 50 });
for (const b of branches) {
console.log(`${b.name} ${b.commit.id.slice(0, 8)} ${b.commit.message}`);
}Commits
const commits = await repo.listCommits({
sha: "main",
path: "src/",
since: "2025-01-01T00:00:00Z",
limit: 20,
});
for (const c of commits) {
console.log(`${c.sha.slice(0, 8)} ${c.author.name} ${c.message}`);
}Mirror
// Shorthand (defaults to GitHub)
const { repository, repo } = await gm.mirror("facebook/react");
// Full URL with options
const { repository } = await gm.mirror("https://gitlab.com/org/repo", {
private: true,
issues: true,
pullRequests: true,
releases: true,
});
// repo is a GitMorphRepo handle, ready to use immediately
const files = await repo.glob({ patterns: ["**/*.ts"] });Error handling
All errors extend GitMorphError:
import {
GitMorphError,
ApiError,
AuthenticationError,
GrepError,
MirrorError,
} from "@morphllm/gitmorph-sdk";
try {
await repo.readFile("missing.txt");
} catch (err) {
if (err instanceof ApiError) {
console.log(err.status, err.url); // 404, https://gitmorph.com/api/v1/...
}
}| Error class | When |
|-------------|------|
| AuthenticationError | No token found in options, env, or config file |
| ApiError | HTTP error from the API (has .status and .url) |
| GrepError | Invalid grep pattern or search failure |
| MirrorError | Mirror/migrate operation failed |
License
MIT
