forage-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +146 -0
- package/dist/cli.js.map +1 -0
- package/dist/persistence/log.d.ts +11 -0
- package/dist/persistence/log.d.ts.map +1 -0
- package/dist/persistence/log.js +21 -0
- package/dist/persistence/log.js.map +1 -0
- package/dist/persistence/manifest.d.ts +24 -0
- package/dist/persistence/manifest.d.ts.map +1 -0
- package/dist/persistence/manifest.js +45 -0
- package/dist/persistence/manifest.js.map +1 -0
- package/dist/proxy/manager.d.ts +20 -0
- package/dist/proxy/manager.d.ts.map +1 -0
- package/dist/proxy/manager.js +77 -0
- package/dist/proxy/manager.js.map +1 -0
- package/dist/proxy/wrapper.d.ts +20 -0
- package/dist/proxy/wrapper.d.ts.map +1 -0
- package/dist/proxy/wrapper.js +37 -0
- package/dist/proxy/wrapper.js.map +1 -0
- package/dist/registries/npm.d.ts +10 -0
- package/dist/registries/npm.d.ts.map +1 -0
- package/dist/registries/npm.js +74 -0
- package/dist/registries/npm.js.map +1 -0
- package/dist/registries/official.d.ts +23 -0
- package/dist/registries/official.d.ts.map +1 -0
- package/dist/registries/official.js +57 -0
- package/dist/registries/official.js.map +1 -0
- package/dist/registries/smithery.d.ts +3 -0
- package/dist/registries/smithery.d.ts.map +1 -0
- package/dist/registries/smithery.js +27 -0
- package/dist/registries/smithery.js.map +1 -0
- package/dist/registries/types.d.ts +13 -0
- package/dist/registries/types.d.ts.map +1 -0
- package/dist/registries/types.js +2 -0
- package/dist/registries/types.js.map +1 -0
- package/dist/rules/detector.d.ts +8 -0
- package/dist/rules/detector.d.ts.map +1 -0
- package/dist/rules/detector.js +54 -0
- package/dist/rules/detector.js.map +1 -0
- package/dist/rules/writer.d.ts +8 -0
- package/dist/rules/writer.d.ts.map +1 -0
- package/dist/rules/writer.js +135 -0
- package/dist/rules/writer.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +167 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/evaluate.d.ts +17 -0
- package/dist/tools/evaluate.d.ts.map +1 -0
- package/dist/tools/evaluate.js +31 -0
- package/dist/tools/evaluate.js.map +1 -0
- package/dist/tools/install.d.ts +18 -0
- package/dist/tools/install.d.ts.map +1 -0
- package/dist/tools/install.js +91 -0
- package/dist/tools/install.js.map +1 -0
- package/dist/tools/learn.d.ts +12 -0
- package/dist/tools/learn.d.ts.map +1 -0
- package/dist/tools/learn.js +31 -0
- package/dist/tools/learn.js.map +1 -0
- package/dist/tools/search.d.ts +10 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +50 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/status.d.ts +20 -0
- package/dist/tools/status.d.ts.map +1 -0
- package/dist/tools/status.js +24 -0
- package/dist/tools/status.js.map +1 -0
- package/dist/tools/uninstall.d.ts +11 -0
- package/dist/tools/uninstall.d.ts.map +1 -0
- package/dist/tools/uninstall.js +55 -0
- package/dist/tools/uninstall.js.map +1 -0
- package/package.json +49 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-02-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- MCP server with 6 tools: `forage_search`, `forage_evaluate`, `forage_install`, `forage_learn`, `forage_status`, `forage_uninstall`
|
|
13
|
+
- Registry search across Official MCP Registry, Smithery, and npm
|
|
14
|
+
- Proxy/gateway pattern — installed tools run as child processes with instant availability via `list_changed` notifications
|
|
15
|
+
- Persistence layer at `~/.forage/` with `manifest.json` (auto-start config) and `install-log.json` (audit trail)
|
|
16
|
+
- Agent rule file support — writes to CLAUDE.md, AGENTS.md, and `.cursor/rules/` with clean HTML comment markers
|
|
17
|
+
- CLI with `forage init`, `forage search`, and `forage list` commands
|
|
18
|
+
- Human approval required for all installs (`confirm: true` safety gate)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Isaac Levine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Forage
|
|
2
|
+
|
|
3
|
+
**Self-improving tool discovery for AI agents.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/forage-mcp)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Forage is an MCP server that lets AI agents discover, install, and learn to use new tools — automatically. When an agent hits a wall, it forages for the right tool, installs it, and teaches itself how to use it. The agent gets permanently smarter.
|
|
9
|
+
|
|
10
|
+
## The Problem
|
|
11
|
+
|
|
12
|
+
AI coding agents are limited to whatever tools they're configured with at session start. When an agent needs to query a database, deploy to Vercel, or search Slack, it apologizes and the human manually installs the right MCP server. This is the bottleneck of agentic development.
|
|
13
|
+
|
|
14
|
+
## The Solution
|
|
15
|
+
|
|
16
|
+
Forage closes the self-improvement loop:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Agent encounters a task it can't do
|
|
20
|
+
→ forage_search("query postgres database")
|
|
21
|
+
→ forage_install("@modelcontextprotocol/server-postgres")
|
|
22
|
+
→ Tools available IMMEDIATELY (no restart)
|
|
23
|
+
→ forage_learn() saves instructions to CLAUDE.md
|
|
24
|
+
→ Next session: auto-starts, agent already knows how to use it
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Claude Code
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude mcp add forage -- npx -y forage-mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Cursor
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx forage-mcp init --client cursor
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then start a new session. Forage is ready.
|
|
42
|
+
|
|
43
|
+
## Tools
|
|
44
|
+
|
|
45
|
+
| Tool | Description |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `forage_search` | Search for MCP servers across the [Official MCP Registry](https://registry.modelcontextprotocol.io), [Smithery](https://smithery.ai), and [npm](https://www.npmjs.com) |
|
|
48
|
+
| `forage_evaluate` | Get details on a package — downloads, README, install command |
|
|
49
|
+
| `forage_install` | Install and start an MCP server as a proxied subprocess (requires user approval) |
|
|
50
|
+
| `forage_learn` | Write usage instructions to CLAUDE.md / AGENTS.md / .cursor/rules/ |
|
|
51
|
+
| `forage_status` | List all installed and running tools |
|
|
52
|
+
| `forage_uninstall` | Remove a tool and clean up rules |
|
|
53
|
+
|
|
54
|
+
## How It Works
|
|
55
|
+
|
|
56
|
+
Forage is an MCP server that acts as a **gateway/proxy**:
|
|
57
|
+
|
|
58
|
+
1. **You install Forage once** — it's the only MCP server you configure manually
|
|
59
|
+
2. **Forage discovers tools** — searches the Official MCP Registry, Smithery, and npm in parallel
|
|
60
|
+
3. **Forage installs tools** — starts them as child processes, wraps their capabilities
|
|
61
|
+
4. **No restart needed** — Forage emits `list_changed` notifications, agent picks up new tools instantly
|
|
62
|
+
5. **Knowledge persists** — `forage_learn` writes to agent rule files, manifest auto-starts tools next session
|
|
63
|
+
|
|
64
|
+
### Architecture
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
┌─────────────────────────────────────────────┐
|
|
68
|
+
│ Claude Code / Cursor / Codex │
|
|
69
|
+
│ │
|
|
70
|
+
│ "I need to query a Postgres database" │
|
|
71
|
+
└──────────────────┬──────────────────────────┘
|
|
72
|
+
│ MCP
|
|
73
|
+
▼
|
|
74
|
+
┌─────────────────────────────────────────────┐
|
|
75
|
+
│ Forage MCP Server │
|
|
76
|
+
│ │
|
|
77
|
+
│ forage_search ─── Official Registry │
|
|
78
|
+
│ forage_install Smithery │
|
|
79
|
+
│ forage_learn npm │
|
|
80
|
+
│ forage_status │
|
|
81
|
+
│ │
|
|
82
|
+
│ ┌─────────────┐ ┌─────────────┐ │
|
|
83
|
+
│ │ Postgres MCP│ │ GitHub MCP │ ... │
|
|
84
|
+
│ │ (subprocess)│ │ (subprocess)│ │
|
|
85
|
+
│ └─────────────┘ └─────────────┘ │
|
|
86
|
+
└─────────────────────────────────────────────┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### The Proxy Pattern
|
|
90
|
+
|
|
91
|
+
When you install a tool through Forage:
|
|
92
|
+
|
|
93
|
+
1. Forage runs `npx -y <package>` as a child process
|
|
94
|
+
2. Connects to it via `StdioClientTransport` (MCP client)
|
|
95
|
+
3. Discovers the child server's tools via `listTools`
|
|
96
|
+
4. Re-registers each tool on the Forage server with a namespaced name (`foraged__<server>__<tool>`)
|
|
97
|
+
5. Sends `tools/list_changed` notification — the agent sees new tools immediately
|
|
98
|
+
6. When the agent calls a proxied tool, Forage forwards the call to the child server
|
|
99
|
+
|
|
100
|
+
## Persistence
|
|
101
|
+
|
|
102
|
+
Forage stores its state in `~/.forage/`:
|
|
103
|
+
|
|
104
|
+
| File | Purpose |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `manifest.json` | Installed tools, command/args, auto-start configuration |
|
|
107
|
+
| `install-log.json` | Audit trail of all installs and uninstalls |
|
|
108
|
+
| `cache/` | Cached registry search results |
|
|
109
|
+
|
|
110
|
+
On startup, Forage reads the manifest and auto-starts all previously installed servers. Your agent picks up right where it left off.
|
|
111
|
+
|
|
112
|
+
## CLI
|
|
113
|
+
|
|
114
|
+
Forage also includes a CLI for humans:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
forage search "postgres database" # Search registries
|
|
118
|
+
forage list # List installed tools
|
|
119
|
+
forage init # Set up for Claude Code
|
|
120
|
+
forage init --client cursor # Set up for Cursor
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Security
|
|
124
|
+
|
|
125
|
+
- **Human approval required** — `forage_install` always requires explicit `confirm: true`. The agent cannot install tools without the user approving the tool call.
|
|
126
|
+
- **Audit trail** — every install/uninstall is logged with timestamps to `~/.forage/install-log.json`
|
|
127
|
+
- **No remote backend** — everything runs locally. Registry searches are read-only GET requests to public APIs.
|
|
128
|
+
- **No secrets stored** — environment variables for child servers are passed at install time, not persisted.
|
|
129
|
+
|
|
130
|
+
## Development
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/isaac-levine/forage.git
|
|
134
|
+
cd forage
|
|
135
|
+
npm install
|
|
136
|
+
npm run build
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Test locally with Claude Code:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
claude mcp add forage-dev -- node /path/to/forage/dist/server.js
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
|
|
146
|
+
|
|
147
|
+
## Roadmap
|
|
148
|
+
|
|
149
|
+
- [ ] `forage update` — check for newer versions of installed tools
|
|
150
|
+
- [ ] Support for pip/cargo/brew packages (not just npm)
|
|
151
|
+
- [ ] Smarter search ranking (weight by downloads, stars, description relevance)
|
|
152
|
+
- [ ] `server.json` submission to the Official MCP Registry
|
|
153
|
+
- [ ] Landing page at forage.dev
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
[MIT](LICENSE)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { forage_search } from "./tools/search.js";
|
|
6
|
+
import { listTools } from "./persistence/manifest.js";
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
const HELP = `
|
|
9
|
+
forage — Self-improving tool discovery for AI agents
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
forage init [--client <name>] Set up Forage for your AI agent
|
|
13
|
+
forage search <query> Search for MCP servers
|
|
14
|
+
forage list List installed tools
|
|
15
|
+
forage help Show this help message
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
forage init Add Forage to Claude Code
|
|
19
|
+
forage init --client cursor Add Forage to Cursor
|
|
20
|
+
forage search "postgres database"
|
|
21
|
+
forage list
|
|
22
|
+
`.trim();
|
|
23
|
+
async function main() {
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
if (args.length === 0 || args[0] === "help" || args[0] === "--help") {
|
|
26
|
+
console.log(HELP);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const command = args[0];
|
|
30
|
+
switch (command) {
|
|
31
|
+
case "init":
|
|
32
|
+
await handleInit(args.slice(1));
|
|
33
|
+
break;
|
|
34
|
+
case "search":
|
|
35
|
+
await handleSearch(args.slice(1));
|
|
36
|
+
break;
|
|
37
|
+
case "list":
|
|
38
|
+
await handleList();
|
|
39
|
+
break;
|
|
40
|
+
default:
|
|
41
|
+
console.error(`Unknown command: ${command}`);
|
|
42
|
+
console.log(HELP);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function handleInit(args) {
|
|
47
|
+
const { values } = parseArgs({
|
|
48
|
+
args,
|
|
49
|
+
options: {
|
|
50
|
+
client: { type: "string", default: "claude-code" },
|
|
51
|
+
},
|
|
52
|
+
allowPositionals: false,
|
|
53
|
+
});
|
|
54
|
+
const client = values.client ?? "claude-code";
|
|
55
|
+
switch (client) {
|
|
56
|
+
case "claude-code": {
|
|
57
|
+
console.log("Adding Forage to Claude Code...");
|
|
58
|
+
try {
|
|
59
|
+
await execAsync("claude mcp add forage -- npx -y forage-mcp");
|
|
60
|
+
console.log("Done! Forage has been added to Claude Code.");
|
|
61
|
+
console.log("Start a new Claude Code session to use it.");
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
console.log("Could not run `claude mcp add` automatically.");
|
|
65
|
+
console.log("Run this command manually:");
|
|
66
|
+
console.log("");
|
|
67
|
+
console.log(" claude mcp add forage -- npx -y forage-mcp");
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "cursor": {
|
|
72
|
+
console.log("Adding Forage to Cursor...");
|
|
73
|
+
const fs = await import("node:fs/promises");
|
|
74
|
+
const path = await import("node:path");
|
|
75
|
+
const mcpConfigPath = path.join(process.cwd(), ".cursor", "mcp.json");
|
|
76
|
+
let config = {};
|
|
77
|
+
try {
|
|
78
|
+
const existing = await fs.readFile(mcpConfigPath, "utf-8");
|
|
79
|
+
config = JSON.parse(existing);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// No existing config
|
|
83
|
+
}
|
|
84
|
+
const mcpServers = config.mcpServers ?? {};
|
|
85
|
+
mcpServers.forage = {
|
|
86
|
+
command: "npx",
|
|
87
|
+
args: ["-y", "forage-mcp"],
|
|
88
|
+
};
|
|
89
|
+
config.mcpServers = mcpServers;
|
|
90
|
+
await fs.mkdir(path.dirname(mcpConfigPath), { recursive: true });
|
|
91
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
92
|
+
console.log(`Written to ${mcpConfigPath}`);
|
|
93
|
+
console.log("Restart Cursor to use Forage.");
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
console.error(`Unsupported client: ${client}`);
|
|
98
|
+
console.log("Supported clients: claude-code, cursor");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function handleSearch(args) {
|
|
103
|
+
const query = args.join(" ");
|
|
104
|
+
if (!query) {
|
|
105
|
+
console.error("Usage: forage search <query>");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
console.log(`Searching for "${query}"...\n`);
|
|
109
|
+
const { results } = await forage_search({ query });
|
|
110
|
+
if (results.length === 0) {
|
|
111
|
+
console.log("No results found.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const result of results) {
|
|
115
|
+
const badge = result.source === "official-registry"
|
|
116
|
+
? "[official]"
|
|
117
|
+
: result.source === "smithery"
|
|
118
|
+
? "[smithery]"
|
|
119
|
+
: "[npm]";
|
|
120
|
+
console.log(` ${badge} ${result.packageName}`);
|
|
121
|
+
console.log(` ${result.description}`);
|
|
122
|
+
if (result.url)
|
|
123
|
+
console.log(` ${result.url}`);
|
|
124
|
+
console.log("");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function handleList() {
|
|
128
|
+
const tools = await listTools();
|
|
129
|
+
if (tools.length === 0) {
|
|
130
|
+
console.log("No tools installed via Forage.");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
console.log("Installed tools:\n");
|
|
134
|
+
for (const tool of tools) {
|
|
135
|
+
console.log(` ${tool.name}`);
|
|
136
|
+
console.log(` Package: ${tool.packageName}`);
|
|
137
|
+
console.log(` Installed: ${tool.installedAt}`);
|
|
138
|
+
console.log(` Auto-start: ${tool.autoStart}`);
|
|
139
|
+
console.log("");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
main().catch((error) => {
|
|
143
|
+
console.error(error);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
|
146
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface LogEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
action: "install" | "uninstall";
|
|
4
|
+
packageName: string;
|
|
5
|
+
version?: string;
|
|
6
|
+
source?: string;
|
|
7
|
+
success: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function appendLog(entry: LogEntry): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/persistence/log.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAaD,wBAAsB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { ensureForageDir } from "./manifest.js";
|
|
5
|
+
const LOG_PATH = path.join(os.homedir(), ".forage", "install-log.json");
|
|
6
|
+
async function readLog() {
|
|
7
|
+
try {
|
|
8
|
+
const data = await fs.readFile(LOG_PATH, "utf-8");
|
|
9
|
+
return JSON.parse(data);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function appendLog(entry) {
|
|
16
|
+
await ensureForageDir();
|
|
17
|
+
const log = await readLog();
|
|
18
|
+
log.push(entry);
|
|
19
|
+
await fs.writeFile(LOG_PATH, JSON.stringify(log, null, 2), "utf-8");
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/persistence/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAYhD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;AAExE,KAAK,UAAU,OAAO;IACpB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAe;IAC7C,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,MAAM,OAAO,EAAE,CAAC;IAC5B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface InstalledTool {
|
|
2
|
+
name: string;
|
|
3
|
+
packageName: string;
|
|
4
|
+
version: string;
|
|
5
|
+
source: "npm" | "official-registry" | "smithery";
|
|
6
|
+
command: string;
|
|
7
|
+
args: string[];
|
|
8
|
+
env?: Record<string, string>;
|
|
9
|
+
autoStart: boolean;
|
|
10
|
+
installedAt: string;
|
|
11
|
+
rules?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface Manifest {
|
|
14
|
+
version: 1;
|
|
15
|
+
tools: Record<string, InstalledTool>;
|
|
16
|
+
}
|
|
17
|
+
export declare function ensureForageDir(): Promise<void>;
|
|
18
|
+
export declare function readManifest(): Promise<Manifest>;
|
|
19
|
+
export declare function writeManifest(manifest: Manifest): Promise<void>;
|
|
20
|
+
export declare function addTool(tool: InstalledTool): Promise<void>;
|
|
21
|
+
export declare function removeTool(name: string): Promise<InstalledTool | null>;
|
|
22
|
+
export declare function getTool(name: string): Promise<InstalledTool | null>;
|
|
23
|
+
export declare function listTools(): Promise<InstalledTool[]>;
|
|
24
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/persistence/manifest.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,GAAG,mBAAmB,GAAG,UAAU,CAAC;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACtC;AAKD,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAOtD;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrE;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhE;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAQ5E;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAGzE;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAG1D"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
const FORAGE_DIR = path.join(os.homedir(), ".forage");
|
|
5
|
+
const MANIFEST_PATH = path.join(FORAGE_DIR, "manifest.json");
|
|
6
|
+
export async function ensureForageDir() {
|
|
7
|
+
await fs.mkdir(FORAGE_DIR, { recursive: true });
|
|
8
|
+
await fs.mkdir(path.join(FORAGE_DIR, "cache"), { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
export async function readManifest() {
|
|
11
|
+
try {
|
|
12
|
+
const data = await fs.readFile(MANIFEST_PATH, "utf-8");
|
|
13
|
+
return JSON.parse(data);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return { version: 1, tools: {} };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function writeManifest(manifest) {
|
|
20
|
+
await ensureForageDir();
|
|
21
|
+
await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest, null, 2), "utf-8");
|
|
22
|
+
}
|
|
23
|
+
export async function addTool(tool) {
|
|
24
|
+
const manifest = await readManifest();
|
|
25
|
+
manifest.tools[tool.name] = tool;
|
|
26
|
+
await writeManifest(manifest);
|
|
27
|
+
}
|
|
28
|
+
export async function removeTool(name) {
|
|
29
|
+
const manifest = await readManifest();
|
|
30
|
+
const tool = manifest.tools[name] ?? null;
|
|
31
|
+
if (tool) {
|
|
32
|
+
delete manifest.tools[name];
|
|
33
|
+
await writeManifest(manifest);
|
|
34
|
+
}
|
|
35
|
+
return tool;
|
|
36
|
+
}
|
|
37
|
+
export async function getTool(name) {
|
|
38
|
+
const manifest = await readManifest();
|
|
39
|
+
return manifest.tools[name] ?? null;
|
|
40
|
+
}
|
|
41
|
+
export async function listTools() {
|
|
42
|
+
const manifest = await readManifest();
|
|
43
|
+
return Object.values(manifest.tools);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/persistence/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAoBzB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AACtD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;AAE7D,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACnC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAkB;IACpD,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,EAAE,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAChF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAmB;IAC/C,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACjC,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAC1C,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAY;IACxC,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import type { InstalledTool } from "../persistence/manifest.js";
|
|
4
|
+
export interface ManagedServer {
|
|
5
|
+
name: string;
|
|
6
|
+
client: Client;
|
|
7
|
+
transport: StdioClientTransport;
|
|
8
|
+
tools: Array<{
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
inputSchema: Record<string, unknown>;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare function startServer(tool: InstalledTool): Promise<ManagedServer>;
|
|
15
|
+
export declare function stopServer(name: string): Promise<boolean>;
|
|
16
|
+
export declare function callTool(serverName: string, toolName: string, args: Record<string, unknown>): Promise<unknown>;
|
|
17
|
+
export declare function getRunningServers(): Map<string, ManagedServer>;
|
|
18
|
+
export declare function getServer(name: string): ManagedServer | undefined;
|
|
19
|
+
export declare function stopAll(): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/proxy/manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACtC,CAAC,CAAC;CACJ;AAID,wBAAsB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAuC7E;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAY/D;AAED,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED,wBAAgB,iBAAiB,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAE9D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAEjE;AAED,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAG7C"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
const servers = new Map();
|
|
4
|
+
export async function startServer(tool) {
|
|
5
|
+
// If already running, return existing
|
|
6
|
+
const existing = servers.get(tool.name);
|
|
7
|
+
if (existing) {
|
|
8
|
+
return existing;
|
|
9
|
+
}
|
|
10
|
+
const transport = new StdioClientTransport({
|
|
11
|
+
command: tool.command,
|
|
12
|
+
args: tool.args,
|
|
13
|
+
env: tool.env
|
|
14
|
+
? { ...filterEnv(process.env), ...tool.env }
|
|
15
|
+
: filterEnv(process.env),
|
|
16
|
+
});
|
|
17
|
+
const client = new Client({ name: `forage-proxy-${tool.name}`, version: "0.1.0" }, { capabilities: {} });
|
|
18
|
+
await client.connect(transport);
|
|
19
|
+
// Discover the child server's tools
|
|
20
|
+
const toolsList = await client.listTools();
|
|
21
|
+
const tools = (toolsList.tools ?? []).map((t) => ({
|
|
22
|
+
name: t.name,
|
|
23
|
+
description: t.description,
|
|
24
|
+
inputSchema: t.inputSchema,
|
|
25
|
+
}));
|
|
26
|
+
const managed = {
|
|
27
|
+
name: tool.name,
|
|
28
|
+
client,
|
|
29
|
+
transport,
|
|
30
|
+
tools,
|
|
31
|
+
};
|
|
32
|
+
servers.set(tool.name, managed);
|
|
33
|
+
return managed;
|
|
34
|
+
}
|
|
35
|
+
export async function stopServer(name) {
|
|
36
|
+
const server = servers.get(name);
|
|
37
|
+
if (!server)
|
|
38
|
+
return false;
|
|
39
|
+
try {
|
|
40
|
+
await server.client.close();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Ignore close errors — transport cleanup handles it
|
|
44
|
+
}
|
|
45
|
+
servers.delete(name);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
export async function callTool(serverName, toolName, args) {
|
|
49
|
+
const server = servers.get(serverName);
|
|
50
|
+
if (!server) {
|
|
51
|
+
throw new Error(`Server "${serverName}" is not running`);
|
|
52
|
+
}
|
|
53
|
+
const result = await server.client.callTool({
|
|
54
|
+
name: toolName,
|
|
55
|
+
arguments: args,
|
|
56
|
+
});
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
export function getRunningServers() {
|
|
60
|
+
return servers;
|
|
61
|
+
}
|
|
62
|
+
export function getServer(name) {
|
|
63
|
+
return servers.get(name);
|
|
64
|
+
}
|
|
65
|
+
export async function stopAll() {
|
|
66
|
+
const names = [...servers.keys()];
|
|
67
|
+
await Promise.all(names.map((name) => stopServer(name)));
|
|
68
|
+
}
|
|
69
|
+
function filterEnv(env) {
|
|
70
|
+
const result = {};
|
|
71
|
+
for (const [key, value] of Object.entries(env)) {
|
|
72
|
+
if (value !== undefined)
|
|
73
|
+
result[key] = value;
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/proxy/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface WrappedTool {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: Record<string, unknown>;
|
|
5
|
+
serverName: string;
|
|
6
|
+
originalName: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get all tools from all running proxied servers, namespaced to avoid collisions.
|
|
10
|
+
* Tool names are prefixed with the server name: "server-postgres__query" -> "foraged__server-postgres__query"
|
|
11
|
+
*/
|
|
12
|
+
export declare function getWrappedTools(): WrappedTool[];
|
|
13
|
+
/**
|
|
14
|
+
* Parse a wrapped tool name back to server name + original tool name.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseWrappedToolName(name: string): {
|
|
17
|
+
serverName: string;
|
|
18
|
+
toolName: string;
|
|
19
|
+
} | null;
|
|
20
|
+
//# sourceMappingURL=wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/proxy/wrapper.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,WAAW,EAAE,CAgB/C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,GACX;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAOjD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getRunningServers } from "./manager.js";
|
|
2
|
+
/**
|
|
3
|
+
* Get all tools from all running proxied servers, namespaced to avoid collisions.
|
|
4
|
+
* Tool names are prefixed with the server name: "server-postgres__query" -> "foraged__server-postgres__query"
|
|
5
|
+
*/
|
|
6
|
+
export function getWrappedTools() {
|
|
7
|
+
const tools = [];
|
|
8
|
+
for (const [serverName, server] of getRunningServers()) {
|
|
9
|
+
for (const tool of server.tools) {
|
|
10
|
+
tools.push({
|
|
11
|
+
name: `foraged__${sanitizeName(serverName)}__${tool.name}`,
|
|
12
|
+
description: `[via ${serverName}] ${tool.description ?? ""}`.trim(),
|
|
13
|
+
inputSchema: tool.inputSchema,
|
|
14
|
+
serverName,
|
|
15
|
+
originalName: tool.name,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return tools;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse a wrapped tool name back to server name + original tool name.
|
|
23
|
+
*/
|
|
24
|
+
export function parseWrappedToolName(name) {
|
|
25
|
+
if (!name.startsWith("foraged__"))
|
|
26
|
+
return null;
|
|
27
|
+
const parts = name.slice("foraged__".length).split("__");
|
|
28
|
+
if (parts.length < 2)
|
|
29
|
+
return null;
|
|
30
|
+
const serverName = parts[0];
|
|
31
|
+
const toolName = parts.slice(1).join("__");
|
|
32
|
+
return { serverName, toolName };
|
|
33
|
+
}
|
|
34
|
+
function sanitizeName(name) {
|
|
35
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=wrapper.js.map
|