opensrc-mcp
# opensrc-mcp
A codemode MCP server for fetching and querying dependency source code.
## Why?
Traditional MCP exposes tools directly to LLMs. This server uses the **codemode pattern**: agents write JavaScript that executes server-side, and only results return. Benefits:
- **Context efficient** - Large source trees stay server-side
- **Batch operations** - One call to search/read multiple files
- **LLMs are better at code** - More training data for JS than tool-calling
## Installation
```bash
npm install -g opensrc-mcp
# or
npx opensrc-mcp
```
## OpenCode Configuration
Add to your OpenCode config (`~/.config/opencode/config.json` or project `opencode.json`):
```json
{
"mcp": {
"opensrc": {
"type": "local",
"command": "npx",
"args": ["-y", "opensrc-mcp"]
}
}
}
```
## Tool
### `execute`
Single tool exposing all operations. Agents write JS that runs server-side; only results return.
```typescript
// Available in sandbox:
declare const opensrc: {
// Read operations
list(): Source[];
has(name: string, version?: string): boolean;
get(name: string): Source | undefined;
files(sourceName: string, glob?: string): Promise<FileEntry[]>;
tree(sourceName: string, options?: { depth?: number }): Promise<TreeNode>;
grep(pattern: string, options?: {
sources?: string[];
include?: string;
maxResults?: number;
}): Promise<GrepResult[]>;
astGrep(sourceName: string, pattern: string, options?: {
glob?: string;
lang?: string | string[];
limit?: number;
}): Promise<AstGrepMatch[]>;
read(sourceName: string, filePath: string): Promise<string>;
readMany(sourceName: string, paths: string[]): Promise<Record<string, string>>;
resolve(spec: string): Promise<ParsedSpec>;
// Mutation operations
fetch(specs: string | string[], options?: { modify?: boolean }): Promise<FetchedSource[]>;
remove(names: string[]): Promise<RemoveResult>;
clean(options?: {
packages?: boolean;
repos?: boolean;
npm?: boolean;
pypi?: boolean;
crates?: boolean;
}): Promise<RemoveResult>;
};
declare const sources: Source[]; // All fetched sources
declare const cwd: string; // Project directory
```
**Examples:**
```javascript
// List all fetched sources
async () => opensrc.list()
// Fetch npm package (auto-detects version from lockfile)
async () => opensrc.fetch("zod")
// Fetch multiple packages
async () => opensrc.fetch(["zod", "drizzle-orm", "hono"])
// Fetch GitHub repo at specific ref
async () => opensrc.fetch("vercel/ai@v3.0.0")
// Fetch from other registries
async () => opensrc.fetch("pypi:requests")
async () => opensrc.fetch("crates:serde")
// Get directory tree
async () => opensrc.tree("zod", { depth: 2 })
// Find TypeScript files
async () => opensrc.files("zod", "**/*.ts")
// Text search
async () => opensrc.grep("parse", { sources: ["zod"], include: "*.ts" })
// AST search (structural pattern matching)
async () => opensrc.astGrep("zod", "function $NAME($$$ARGS)", { glob: "**/*.ts" })
// Read a specific file
async () => opensrc.read("zod", "src/index.ts")
// Read multiple files (supports globs)
async () => opensrc.readMany("zod", ["src/index.ts", "packages/*/package.json"])
// Remove a source
async () => opensrc.remove(["zod"])
// Clean all npm packages
async () => opensrc.clean({ npm: true })
```
## Package Formats
| Format | Example | Description |
|--------|---------|-------------|
| `<name>` | `zod` | npm (auto-detects version) |
| `<name>@<version>` | `zod@3.22.0` | npm specific version |
| `npm:<name>` | `npm:react` | explicit npm |
| `pypi:<name>` | `pypi:requests` | Python/PyPI |
| `pip:<name>` | `pip:flask` | alias for pypi |
| `crates:<name>` | `crates:serde` | Rust/crates.io |
| `cargo:<name>` | `cargo:tokio` | alias for crates |
| `owner/repo` | `vercel/ai` | GitHub repo |
| `owner/repo@ref` | `vercel/ai@v1.0.0` | GitHub at ref |
| `github:owner/repo` | `github:facebook/react` | explicit GitHub |
## Storage
Sources are stored globally at `~/.local/share/opensrc/` (XDG compliant):
```
~/.local/share/opensrc/
├── sources.json # Index of fetched sources
├── packages/ # npm/pypi/crates packages
│ └── zod/
│ ├── src/
│ ├── package.json
│ └── ...
└── repos/ # GitHub repos
└── github.com/
└── vercel/
└── ai/
```
Override with `$OPENSRC_DIR` or `$XDG_DATA_HOME`.
## How It Works
1. Agent calls `execute` tool with JS code: `async () => opensrc.fetch("zod")`
2. Code runs in sandboxed `vm` context with injected `opensrc` API
3. Server fetches package via [opensrc](https://github.com/vercel-labs/opensrc) (handles registry lookup, git clone)
4. Only the result returns to agent context
```
┌─────────────────────────────────────────────────────────────┐
│ Agent Context │
├─────────────────────────────────────────────────────────────┤
│ Tool call: execute({ code: "async () => opensrc.fetch..." })│
│ ↓ │
│ Result: { success: true, source: { name: "zod", ... } } │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ opensrc-mcp Server │
├─────────────────────────────────────────────────────────────┤
│ Sandbox executes code with injected opensrc API │
│ Full source tree stays here, never sent to agent │
└─────────────────────────────────────────────────────────────┘
```
## License
MIT
TDQS
Scored across 1 tool
With only one tool, there is no possibility of confusion between tools. The single tool encompasses a well-defined API, and its purpose is clearly described in the description.
The server has a single tool named 'execute', which is consistent in itself. While the name is generic, there are no other tools to compare against, so no naming inconsistencies exist.
The domain covers fetching, searching, reading, and managing source code from multiple ecosystems, which is a broad scope. Exposing all of this through a single monolithic tool is overly condensed; separate tools for distinct operations (e.g., fetch, read, grep) would be more appropriate for agent discoverability.
The tool covers listing, fetching, reading, searching (grep and astGrep), and removal of sources, which is comprehensive for exploration. However, it lacks explicit update/edit capabilities for source files and no direct way to modify code, leaving a minor gap in mutation workflows.