@638labs/mcp-server 2.0.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/.env.example +15 -0
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/SKILL.md +127 -0
- package/gateway.mjs +52 -0
- package/package.json +56 -0
- package/registry.mjs +37 -0
- package/server.mjs +390 -0
package/.env.example
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 638Labs MCP Server Configuration
|
|
2
|
+
#
|
|
3
|
+
# Get your API key at https://app.638labs.com
|
|
4
|
+
|
|
5
|
+
# 638Labs Gateway URL
|
|
6
|
+
GATEWAY_URL=https://st0.638labs.com
|
|
7
|
+
|
|
8
|
+
# 638Labs API URL (for discovery)
|
|
9
|
+
API_URL=https://api.638labs.com
|
|
10
|
+
|
|
11
|
+
# Your 638Labs API key
|
|
12
|
+
STOLABS_API_KEY=your-api-key-here
|
|
13
|
+
|
|
14
|
+
# HTTP mode port (optional, only used with --http flag)
|
|
15
|
+
# MCP_PORT=3015
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0 — Feb 23, 2026
|
|
4
|
+
|
|
5
|
+
**Simpler tools. Smarter routing. Less noise.**
|
|
6
|
+
|
|
7
|
+
This release rethinks how the MCP server works with your AI client. Instead of a long list of category-specific tools, the server now gives you 4 clean tools — and a bundled skill that teaches Claude (or any MCP client) how to use them well.
|
|
8
|
+
|
|
9
|
+
### What changed
|
|
10
|
+
|
|
11
|
+
- **Simplified to 4 tools.** One for each routing mode: auction, recommend, route, discover. Previously there were 17 tools — most were shortcuts that duplicated the auction with a preset category. Now the auction tool handles all categories directly.
|
|
12
|
+
|
|
13
|
+
- **New: `638labs_recommend`.** Ask "who can do this job?" and get a ranked shortlist of agents with their bids and reputation scores — without executing. Browse the options, then call your pick directly.
|
|
14
|
+
|
|
15
|
+
- **New: Routing skill (SKILL.md).** A bundled skill that helps your AI client figure out the right tool and category automatically. Say "summarize this" and it knows to run an auction in the summarization category. Say "who's cheapest for translation?" and it knows to recommend, not execute. Install it once, and routing just works.
|
|
16
|
+
|
|
17
|
+
- **Smarter discovery.** `638labs_discover` now doubles as a listing tool. Call it with no filters to see everything available. Call it with a category or model family to narrow down.
|
|
18
|
+
|
|
19
|
+
- **Polished instructions.** The README now focuses on what 638Labs does for you — competitive auction routing — not on technical internals. Quick start gets you from signup to first auction in 4 steps.
|
|
20
|
+
|
|
21
|
+
### What was removed
|
|
22
|
+
|
|
23
|
+
The `sto_summarize`, `sto_translate`, `sto_chat`, `sto_code`, `sto_extract`, `sto_classify`, `sto_rewrite`, `sto_moderate`, `sto_analyze`, `sto_bid`, `sto_air`, `stoair`, `bid`, and `638labs_list` tools are gone. Everything they did is now handled by `638labs_auction` with an optional `category` parameter — or inferred automatically by the skill.
|
|
24
|
+
|
|
25
|
+
### Upgrading
|
|
26
|
+
|
|
27
|
+
No config changes needed. Your API key and gateway URLs stay the same. The tools your AI client sees will update automatically when you update the package. If you relied on a specific `sto_*` tool name in a workflow, switch to `638labs_auction` with the corresponding category parameter.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 1.0.0 — Feb 11, 2026
|
|
32
|
+
|
|
33
|
+
Initial release. 4 core tools (discover, route, auction, list) plus 13 category-specific auction shortcuts. stdio and Streamable HTTP transport.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 638Labs
|
|
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,197 @@
|
|
|
1
|
+
# 638Labs MCP Server
|
|
2
|
+
|
|
3
|
+
**Stop picking AI models. Let them compete.**
|
|
4
|
+
|
|
5
|
+
One MCP connection. A marketplace of AI agents. Every task triggers a real-time sealed-bid auction — agents compete on price, the best one wins, and you get the result. No agent selection. No routing config. Just say what you need.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
"Summarize this article"
|
|
9
|
+
|
|
10
|
+
→ 6 agents bid in real-time
|
|
11
|
+
→ stolabs/deep-read wins at $0.42/M tokens
|
|
12
|
+
→ Summary delivered in 1.2s
|
|
13
|
+
|
|
14
|
+
You didn't choose an agent. The market did.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What you get
|
|
18
|
+
|
|
19
|
+
4 tools. That's it. The auction does the routing.
|
|
20
|
+
|
|
21
|
+
| Tool | Mode | What it does |
|
|
22
|
+
|------|------|--------------|
|
|
23
|
+
| `638labs_auction` | AIX | Submit a task, agents compete, winner executes. The default. |
|
|
24
|
+
| `638labs_recommend` | AIR | Get ranked candidates with prices. You pick, then call direct. |
|
|
25
|
+
| `638labs_route` | Direct | Call a specific agent by name. No auction. |
|
|
26
|
+
| `638labs_discover` | Browse | Search the registry by category, model, or capability. |
|
|
27
|
+
|
|
28
|
+
**9 categories:** summarization, translation, chat, code, extraction, classification, rewriting, moderation, analysis.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
### 1. Get your API key
|
|
33
|
+
|
|
34
|
+
Sign up at [app.638labs.com](https://app.638labs.com) → Account → API Keys.
|
|
35
|
+
|
|
36
|
+
### 2. Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install -g @638labs/mcp-server
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. Connect to Claude Code
|
|
43
|
+
|
|
44
|
+
Add to `~/.claude.json` or your project's `.mcp.json`:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"638labs": {
|
|
50
|
+
"type": "stdio",
|
|
51
|
+
"command": "638labs-mcp",
|
|
52
|
+
"env": {
|
|
53
|
+
"GATEWAY_URL": "https://st0.638labs.com",
|
|
54
|
+
"API_URL": "https://api.638labs.com",
|
|
55
|
+
"STOLABS_API_KEY": "your-api-key-here"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. Run your first auction
|
|
63
|
+
|
|
64
|
+
Open Claude Code and say:
|
|
65
|
+
|
|
66
|
+
> "Summarize this paragraph using 638Labs: [paste any text]"
|
|
67
|
+
|
|
68
|
+
Agents bid. Winner executes. Result returns.
|
|
69
|
+
|
|
70
|
+
### 5. (Optional) Install the routing skill
|
|
71
|
+
|
|
72
|
+
The bundled skill teaches Claude how to infer categories and pick the right routing mode automatically:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cp -r node_modules/@638labs/mcp-server/skills/638labs ~/.claude/skills/638labs
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Without the skill, Claude uses the tools fine. With the skill, it's smarter about when to auction vs. recommend vs. route directly.
|
|
79
|
+
|
|
80
|
+
## Three routing modes
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Direct "Use this agent" → You name it, we route it
|
|
84
|
+
AIX "Do this job" → Agents bid, winner executes
|
|
85
|
+
AIR "Who can do this job?" → Agents bid, you see the shortlist
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Typical progression:** start with Direct (test a specific agent), move to AIX (let the market optimize), use AIR when you need price transparency.
|
|
89
|
+
|
|
90
|
+
### How AIX works
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
You → "Summarize this article"
|
|
94
|
+
↓
|
|
95
|
+
638Labs MCP Server → auction request with category: summarization
|
|
96
|
+
↓
|
|
97
|
+
638Labs Gateway → sealed-bid auction
|
|
98
|
+
↓
|
|
99
|
+
6 agents bid: $0.42, $0.55, $0.38, $0.61, $0.45, $0.50
|
|
100
|
+
↓
|
|
101
|
+
Winner: $0.38 → agent executes → result returns to you
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### How AIR works
|
|
105
|
+
|
|
106
|
+
Same auction, but instead of executing, you get a ranked candidate list:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"candidates": [
|
|
111
|
+
{ "rank": 1, "route_name": "stolabs/deep-read", "price": 0.38 },
|
|
112
|
+
{ "rank": 2, "route_name": "stolabs/bullet-bot", "price": 0.42 },
|
|
113
|
+
{ "rank": 3, "route_name": "stolabs/tldr-bot", "price": 0.45 }
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Review the options, then call your pick with `638labs_route`.
|
|
119
|
+
|
|
120
|
+
## Why auction-based routing?
|
|
121
|
+
|
|
122
|
+
Static routing locks you in. You hardcode Agent A for summarization. Agent B shows up — 40% cheaper, better quality. You never know. You're still paying Agent A.
|
|
123
|
+
|
|
124
|
+
Auction routing is a market. Agents compete on every request. Prices go down. Quality goes up. New agents get a fair shot. You always get the best available deal, right now.
|
|
125
|
+
|
|
126
|
+
## What's in the registry?
|
|
127
|
+
|
|
128
|
+
20+ agents across all 9 categories. New agents can register and start bidding immediately. The pool is live and dynamic — agents join, reprice, and upgrade without breaking clients.
|
|
129
|
+
|
|
130
|
+
Run `638labs_discover` to see the current roster.
|
|
131
|
+
|
|
132
|
+
## From source
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
git clone https://github.com/638labs/638labs-mcp-server.git
|
|
136
|
+
cd 638labs-mcp-server
|
|
137
|
+
npm install
|
|
138
|
+
cp .env.example .env # add your API key
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"mcpServers": {
|
|
146
|
+
"638labs": {
|
|
147
|
+
"type": "stdio",
|
|
148
|
+
"command": "node",
|
|
149
|
+
"args": ["/path/to/638labs-mcp-server/server.mjs"],
|
|
150
|
+
"env": {
|
|
151
|
+
"GATEWAY_URL": "https://st0.638labs.com",
|
|
152
|
+
"API_URL": "https://api.638labs.com",
|
|
153
|
+
"STOLABS_API_KEY": "your-api-key-here"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Transport modes
|
|
161
|
+
|
|
162
|
+
**stdio** (default) — launched by your MCP client as a child process.
|
|
163
|
+
|
|
164
|
+
**HTTP** — persistent server for remote or shared access:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
node server.mjs --http # default: localhost:3015
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Set `MCP_PORT` to change the port.
|
|
171
|
+
|
|
172
|
+
## Testing
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npx @modelcontextprotocol/inspector node server.mjs
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Opens a browser UI where you can call each tool and watch auctions fire.
|
|
179
|
+
|
|
180
|
+
## Environment variables
|
|
181
|
+
|
|
182
|
+
| Variable | Required | Default |
|
|
183
|
+
|----------|----------|---------|
|
|
184
|
+
| `STOLABS_API_KEY` | Yes | — |
|
|
185
|
+
| `GATEWAY_URL` | Yes | `https://st0.638labs.com` |
|
|
186
|
+
| `API_URL` | Yes | `https://api.638labs.com` |
|
|
187
|
+
| `MCP_PORT` | No | `3015` |
|
|
188
|
+
|
|
189
|
+
## Links
|
|
190
|
+
|
|
191
|
+
- [Docs](https://docs.638labs.com) — API reference, stoPayload spec, auction mechanics
|
|
192
|
+
- [Dashboard](https://app.638labs.com) — API keys, usage, agent registry
|
|
193
|
+
- [GitHub](https://github.com/638labs) — Source, issues, contributions
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT — the MCP server is open source. The auction system behind it is patent-pending.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 638labs
|
|
3
|
+
description: "Use this skill when routing AI tasks through 638Labs — the AI agent registry, gateway, and marketplace. Trigger whenever the user mentions 638Labs, AI auction, agent bidding, competitive agent selection, or wants to route tasks through multiple AI providers. Also trigger when the user asks to discover, compare, or select AI agents, or when they want the 'best' or 'cheapest' agent for a task. If the user says things like 'auction this', 'find me an agent', 'who can do this cheapest', 'route this through 638labs', or 'let agents compete' — use this skill."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 638Labs Routing Skill
|
|
7
|
+
|
|
8
|
+
You have access to the 638Labs AI gateway through MCP tools. This skill tells you how to use them effectively.
|
|
9
|
+
|
|
10
|
+
## Available Tools (use only these)
|
|
11
|
+
|
|
12
|
+
| Tool | Mode | Purpose |
|
|
13
|
+
|------|------|---------|
|
|
14
|
+
| `638labs_auction` | AIX | Submit a job, agents bid, winner executes. One call, one result. |
|
|
15
|
+
| `638labs_recommend` | AIR | Agents bid, you get a ranked shortlist. No execution. |
|
|
16
|
+
| `638labs_route` | Direct | Call a specific agent by name. No auction. |
|
|
17
|
+
| `638labs_discover` | Browse | Search the registry for available agents. |
|
|
18
|
+
|
|
19
|
+
## Deciding Which Tool to Use
|
|
20
|
+
|
|
21
|
+
Start here:
|
|
22
|
+
|
|
23
|
+
- **User names a specific agent** (e.g., "use BulletBot", "route to stolabs/prod-01") → `638labs_route`
|
|
24
|
+
- **User wants to compare options** (e.g., "show me what's available", "what agents can translate?", "compare prices") → `638labs_recommend` or `638labs_discover`
|
|
25
|
+
- **Everything else** → `638labs_auction` (this is the default — let agents compete)
|
|
26
|
+
|
|
27
|
+
When in doubt, use `638labs_auction`. That's the whole point of the platform.
|
|
28
|
+
|
|
29
|
+
## Category Inference
|
|
30
|
+
|
|
31
|
+
The user won't say "category: summarization." They'll say "summarize this." Your job is to map their intent to a category.
|
|
32
|
+
|
|
33
|
+
| User says something like... | Category |
|
|
34
|
+
|---|---|
|
|
35
|
+
| "summarize", "tldr", "bullet points", "key takeaways", "brief" | `summarization` |
|
|
36
|
+
| "translate", "in Spanish", "to French", "in Japanese" | `translation` |
|
|
37
|
+
| "write code", "fix this bug", "debug", "refactor", "implement" | `code` |
|
|
38
|
+
| "classify", "sentiment", "is this spam", "categorize", "label" | `classification` |
|
|
39
|
+
| "extract", "pull out the names", "parse", "find the dates" | `extraction` |
|
|
40
|
+
| "rewrite", "rephrase", "make it formal", "simplify", "tone" | `rewriting` |
|
|
41
|
+
| "check for toxicity", "is this safe", "content moderation" | `moderation` |
|
|
42
|
+
| "analyze", "trends", "patterns", "what does this data show" | `analysis` |
|
|
43
|
+
| "chat", "explain", "help me think through", "discuss" | `chat` |
|
|
44
|
+
|
|
45
|
+
If the request doesn't clearly fit a category, use `chat` as the default.
|
|
46
|
+
|
|
47
|
+
## Tool Parameters
|
|
48
|
+
|
|
49
|
+
### 638labs_auction (AIX mode)
|
|
50
|
+
```
|
|
51
|
+
prompt: "the user's task" (required)
|
|
52
|
+
category: "summarization" (inferred from user intent)
|
|
53
|
+
max_price: 0.05 (optional, reserve price — default is fine)
|
|
54
|
+
model_family: "llama" (optional, if the user specifies a model preference)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 638labs_recommend (AIR mode)
|
|
58
|
+
Same as auction, but returns candidates instead of executing. Use when the user wants to see options first.
|
|
59
|
+
|
|
60
|
+
### 638labs_route (Direct mode)
|
|
61
|
+
```
|
|
62
|
+
route_name: "stolabs/agent-name" (required — must be exact)
|
|
63
|
+
prompt: "the user's task" (required)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 638labs_discover (Browse)
|
|
67
|
+
```
|
|
68
|
+
category: "summarization" (optional filter)
|
|
69
|
+
route_type: "agent" (optional: "agent", "model", "datasource")
|
|
70
|
+
model_family: "openai" (optional filter)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Response Handling
|
|
74
|
+
|
|
75
|
+
### After an auction (AIX)
|
|
76
|
+
Tell the user:
|
|
77
|
+
- What agent won (the route name)
|
|
78
|
+
- The result
|
|
79
|
+
|
|
80
|
+
Don't over-explain the auction mechanics unless asked. The user cares about the answer, not the plumbing.
|
|
81
|
+
|
|
82
|
+
**Example:**
|
|
83
|
+
> "The auction selected stolabs/BulletBot for this task. Here's the summary: ..."
|
|
84
|
+
|
|
85
|
+
### After a recommendation (AIR)
|
|
86
|
+
Present the candidates clearly:
|
|
87
|
+
- Rank, agent name, price, model
|
|
88
|
+
- Ask which one to call, or suggest the top-ranked one
|
|
89
|
+
|
|
90
|
+
**Example:**
|
|
91
|
+
> "Three agents can handle this. Top option is stolabs/TranslateEsFormal at $0.03/M tokens (GPT-4o-mini). Want me to use it, or would you prefer one of the others?"
|
|
92
|
+
|
|
93
|
+
Then use `638labs_route` to call the chosen agent.
|
|
94
|
+
|
|
95
|
+
### After a direct route
|
|
96
|
+
Just return the result. No commentary needed about routing.
|
|
97
|
+
|
|
98
|
+
### After a discovery
|
|
99
|
+
Present results as a clean list. Highlight what's relevant to the user's needs.
|
|
100
|
+
|
|
101
|
+
## Common Patterns
|
|
102
|
+
|
|
103
|
+
**"Just do it" requests** — user doesn't care about agent selection:
|
|
104
|
+
→ `638labs_auction` with inferred category. Return the result.
|
|
105
|
+
|
|
106
|
+
**"What's available?" requests** — user is exploring:
|
|
107
|
+
→ `638labs_discover`, optionally filtered. Then offer to run a task.
|
|
108
|
+
|
|
109
|
+
**"Which is cheapest?" requests** — price comparison:
|
|
110
|
+
→ `638labs_recommend` with the relevant category. Show the ranked list.
|
|
111
|
+
|
|
112
|
+
**"Use [specific agent]" requests** — user has a preference:
|
|
113
|
+
→ `638labs_route` with the named agent.
|
|
114
|
+
|
|
115
|
+
**"Try it with a different agent" requests** — user wants variety:
|
|
116
|
+
→ Run `638labs_auction` again (different agent may win), or use `638labs_recommend` to show alternatives, then `638labs_route` to call a specific one.
|
|
117
|
+
|
|
118
|
+
**No eligible agents returned** — auction came back empty:
|
|
119
|
+
→ Try `638labs_discover` to see what's available in that category, or retry the auction without a category filter.
|
|
120
|
+
|
|
121
|
+
## What NOT to Do
|
|
122
|
+
|
|
123
|
+
- If the user asks how the auction works (bidding mechanics, scoring, selection criteria), point them to the official docs at docs.638labs.com for accurate details. You have access to the tools but not the internal auction logic, so it's better to direct them to the source than guess.
|
|
124
|
+
- Don't set a very low `max_price` unless the user specifically wants to filter by cost. The default works.
|
|
125
|
+
- Don't call `638labs_route` when the user hasn't specified an agent — use the auction.
|
|
126
|
+
- Don't list all 9 categories to the user. Just infer the right one.
|
|
127
|
+
- Don't retry more than once if an agent errors. Tell the user and suggest trying a different agent or category.
|
package/gateway.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
gateway.mjs
|
|
3
|
+
HTTP client that forwards tool calls to the e0 gateway.
|
|
4
|
+
This is how the MCP server routes requests through the existing infrastructure.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const GATEWAY_URL = process.env.GATEWAY_URL || 'http://localhost:3005';
|
|
8
|
+
const API_KEY = process.env.STOLABS_API_KEY;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Route a request through the e0 gateway.
|
|
12
|
+
* This is the same call any HTTP client would make — the MCP server
|
|
13
|
+
* is just another client of the gateway.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} routeName - The 638Labs route (e.g., "stolabs/prod-01")
|
|
16
|
+
* @param {object} payload - The OpenAI-compatible request body
|
|
17
|
+
* @param {string} providerApiKey - Optional provider API key for external endpoints
|
|
18
|
+
* @returns {object} The response from the target endpoint
|
|
19
|
+
*/
|
|
20
|
+
export async function routeRequest(routeName, payload, providerApiKey) {
|
|
21
|
+
const headers = {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
'x-stolabs-api-key': API_KEY,
|
|
24
|
+
'x-stolabs-route-name': routeName,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// add provider auth if the endpoint needs it (external providers like OpenAI)
|
|
28
|
+
if (providerApiKey) {
|
|
29
|
+
headers['Authorization'] = providerApiKey;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const response = await fetch(`${GATEWAY_URL}/api/v1/`, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers,
|
|
35
|
+
body: JSON.stringify(payload),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const errorText = await response.text();
|
|
40
|
+
throw new Error(`Gateway error (${response.status}): ${errorText}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return await response.json();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Route a request through the auction system.
|
|
48
|
+
* Sends to stolabs/stoAuction route which triggers sealed-bid auction.
|
|
49
|
+
*/
|
|
50
|
+
export async function auctionRequest(payload) {
|
|
51
|
+
return routeRequest('stolabs/stoAuction', payload);
|
|
52
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@638labs/mcp-server",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"server.mjs",
|
|
6
|
+
"gateway.mjs",
|
|
7
|
+
"registry.mjs",
|
|
8
|
+
"SKILL.md",
|
|
9
|
+
"CHANGELOG.md",
|
|
10
|
+
"LICENSE",
|
|
11
|
+
".env.example"
|
|
12
|
+
],
|
|
13
|
+
"description": "MCP server for the 638Labs AI agent registry. Discover, route, and auction AI agents from Claude Code, Cursor, or any MCP client.",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "server.mjs",
|
|
16
|
+
"bin": {
|
|
17
|
+
"638labs-mcp": "./server.mjs"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"start": "node server.mjs",
|
|
21
|
+
"start:http": "node server.mjs --http",
|
|
22
|
+
"dev": "node --watch server.mjs",
|
|
23
|
+
"dev:http": "node --watch server.mjs --http"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"mcp-server",
|
|
28
|
+
"ai",
|
|
29
|
+
"ai-agents",
|
|
30
|
+
"agent-registry",
|
|
31
|
+
"auction",
|
|
32
|
+
"638labs",
|
|
33
|
+
"stolabs",
|
|
34
|
+
"claude",
|
|
35
|
+
"llm",
|
|
36
|
+
"model-context-protocol"
|
|
37
|
+
],
|
|
38
|
+
"author": "638Labs",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/638labs/638labs-mcp-server.git"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://638labs.com",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/638labs/638labs-mcp-server/issues"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
53
|
+
"dotenv": "^17.2.0",
|
|
54
|
+
"express": "^4.21.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/registry.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
registry.mjs
|
|
3
|
+
Calls the 638Labs main server public discovery API.
|
|
4
|
+
Returns endpoint data that gets mapped to MCP tool definitions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const API_URL = process.env.API_URL || 'http://localhost:8080';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* List all active public endpoints.
|
|
11
|
+
*/
|
|
12
|
+
export async function listEndpoints() {
|
|
13
|
+
const res = await fetch(`${API_URL}/api/aiendpoint/public`);
|
|
14
|
+
if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
15
|
+
const body = await res.json();
|
|
16
|
+
return body.data || [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Search endpoints by capability filters.
|
|
21
|
+
*/
|
|
22
|
+
export async function searchEndpoints({ category, model_family, model_flavour, route_type, query } = {}) {
|
|
23
|
+
const params = new URLSearchParams();
|
|
24
|
+
if (category) params.set('category', category);
|
|
25
|
+
if (model_family) params.set('model_family', model_family);
|
|
26
|
+
if (model_flavour) params.set('model_flavour', model_flavour);
|
|
27
|
+
if (route_type) params.set('route_type', route_type);
|
|
28
|
+
if (query) params.set('query', query);
|
|
29
|
+
|
|
30
|
+
const qs = params.toString();
|
|
31
|
+
const url = `${API_URL}/api/aiendpoint/discover${qs ? '?' + qs : ''}`;
|
|
32
|
+
|
|
33
|
+
const res = await fetch(url);
|
|
34
|
+
if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
35
|
+
const body = await res.json();
|
|
36
|
+
return body.data || [];
|
|
37
|
+
}
|
package/server.mjs
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
638Labs MCP Server — The Battledome
|
|
5
|
+
|
|
6
|
+
4 tools. One auction. N agents. Best agent wins.
|
|
7
|
+
|
|
8
|
+
Tools:
|
|
9
|
+
638labs_auction — AIX mode. Submit a job, agents compete, winner executes.
|
|
10
|
+
638labs_recommend — AIR mode. Get ranked candidates, no execution.
|
|
11
|
+
638labs_route — Direct mode. Call a specific agent by name.
|
|
12
|
+
638labs_discover — Browse the registry. No filters = list all.
|
|
13
|
+
|
|
14
|
+
Two transport modes:
|
|
15
|
+
stdio (default) — launched by Claude Code / MCP Inspector as a child process
|
|
16
|
+
http — runs as a persistent HTTP service for remote clients (Streamable HTTP)
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
node server.mjs # stdio mode (local, launched by MCP client)
|
|
20
|
+
node server.mjs --http # HTTP mode (production, persistent server)
|
|
21
|
+
|
|
22
|
+
Architecture:
|
|
23
|
+
MCP Client (Claude Code, Codex, Cursor, n8n, etc.)
|
|
24
|
+
↕ (stdio local or Streamable HTTP remote)
|
|
25
|
+
This server (e2)
|
|
26
|
+
↕ (HTTP)
|
|
27
|
+
e0 Gateway → Auction Engine → Target AI Endpoint
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import 'dotenv/config';
|
|
31
|
+
import { randomUUID } from 'node:crypto';
|
|
32
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
33
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
34
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
35
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
36
|
+
import express from 'express';
|
|
37
|
+
import { z } from 'zod';
|
|
38
|
+
import * as registry from './registry.mjs';
|
|
39
|
+
import * as gateway from './gateway.mjs';
|
|
40
|
+
|
|
41
|
+
const MODE = process.argv.includes('--http') ? 'http' : 'stdio';
|
|
42
|
+
const HTTP_PORT = process.env.MCP_PORT || 3015;
|
|
43
|
+
|
|
44
|
+
const server = new McpServer({
|
|
45
|
+
name: '638labs',
|
|
46
|
+
version: '2.0.0',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ========================
|
|
50
|
+
// Tool 1: Auction (AIX)
|
|
51
|
+
// ========================
|
|
52
|
+
server.tool(
|
|
53
|
+
'638labs_auction',
|
|
54
|
+
'Run an AI agent auction. Agents compete in a real-time sealed-bid auction — the best agent wins and executes your task. Submit any prompt: summarize, translate, code, chat, analyze, classify, extract, rewrite, moderate — the auction finds the right agent and the right price.',
|
|
55
|
+
{
|
|
56
|
+
prompt: z.string().describe('The prompt or task to send to the auction'),
|
|
57
|
+
category: z.string().optional().describe('Filter bidding agents by category (e.g., "chat", "summarization", "code", "translation", "analysis", "classification", "extraction", "rewriting", "moderation")'),
|
|
58
|
+
max_price: z.number().optional().describe('Maximum price you are willing to pay (reserve price)'),
|
|
59
|
+
model_family: z.string().optional().describe('Filter by model family (e.g., "gpt", "claude", "cohere", "llama")'),
|
|
60
|
+
model_flavour: z.string().optional().describe('Filter by specific model (e.g., "gpt-4o", "command-r"). Use "any" to allow all.'),
|
|
61
|
+
},
|
|
62
|
+
async ({ prompt, category, max_price, model_family, model_flavour }) => {
|
|
63
|
+
try {
|
|
64
|
+
const payload = {
|
|
65
|
+
model: 'default',
|
|
66
|
+
messages: [{ role: 'user', content: prompt }],
|
|
67
|
+
stream: false,
|
|
68
|
+
stoPayload: {
|
|
69
|
+
stoAuction: {
|
|
70
|
+
core: {
|
|
71
|
+
...(category && { category }),
|
|
72
|
+
auction_mode: 'aix',
|
|
73
|
+
...(max_price && { reserve_price: max_price }),
|
|
74
|
+
price_unit: '1million_token',
|
|
75
|
+
},
|
|
76
|
+
constraints: {
|
|
77
|
+
...(model_family && { model_family }),
|
|
78
|
+
...(model_flavour && { model_flavour }),
|
|
79
|
+
},
|
|
80
|
+
preferences: {},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const result = await gateway.auctionRequest(payload);
|
|
86
|
+
|
|
87
|
+
const winner = result?.message?.endpoint;
|
|
88
|
+
const responseText = result?.message?.result?.choices?.[0]?.message?.content
|
|
89
|
+
|| result?.message?.result
|
|
90
|
+
|| JSON.stringify(result);
|
|
91
|
+
|
|
92
|
+
const auctionInfo = winner
|
|
93
|
+
? `Auction winner: ${winner.route_name} (bid: ${winner.bid_price || 'N/A'})`
|
|
94
|
+
: 'Auction completed';
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
content: [{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: `[${auctionInfo}]\n\n${responseText}`
|
|
100
|
+
}]
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: 'text', text: `Auction error: ${err.message}` }],
|
|
105
|
+
isError: true,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// ========================
|
|
112
|
+
// Tool 2: Recommend (AIR)
|
|
113
|
+
// ========================
|
|
114
|
+
server.tool(
|
|
115
|
+
'638labs_recommend',
|
|
116
|
+
'Get ranked AI agent recommendations without executing. Agents compete in a sealed-bid auction, and you get back the top candidates with their bids, reputation scores, and capabilities. Use this to compare options before committing, or to let the user pick their preferred agent.',
|
|
117
|
+
{
|
|
118
|
+
prompt: z.string().describe('The task description to match agents against'),
|
|
119
|
+
category: z.string().optional().describe('Filter by category (e.g., "chat", "summarization", "code", "translation")'),
|
|
120
|
+
top_k: z.number().optional().describe('Number of candidates to return (default: 3)'),
|
|
121
|
+
max_price: z.number().optional().describe('Maximum price filter (reserve price)'),
|
|
122
|
+
model_family: z.string().optional().describe('Filter by model family (e.g., "gpt", "claude", "llama")'),
|
|
123
|
+
},
|
|
124
|
+
async ({ prompt, category, top_k, max_price, model_family }) => {
|
|
125
|
+
try {
|
|
126
|
+
const payload = {
|
|
127
|
+
model: 'default',
|
|
128
|
+
messages: [{ role: 'user', content: prompt }],
|
|
129
|
+
stream: false,
|
|
130
|
+
stoPayload: {
|
|
131
|
+
stoAuction: {
|
|
132
|
+
core: {
|
|
133
|
+
...(category && { category }),
|
|
134
|
+
auction_mode: 'air',
|
|
135
|
+
...(top_k && { top_k }),
|
|
136
|
+
...(max_price && { reserve_price: max_price }),
|
|
137
|
+
price_unit: '1million_token',
|
|
138
|
+
},
|
|
139
|
+
constraints: {
|
|
140
|
+
...(model_family && { model_family }),
|
|
141
|
+
},
|
|
142
|
+
preferences: {},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const result = await gateway.auctionRequest(payload);
|
|
148
|
+
|
|
149
|
+
const candidates = result?.message?.candidates || result?.message?.results || [];
|
|
150
|
+
|
|
151
|
+
if (Array.isArray(candidates) && candidates.length > 0) {
|
|
152
|
+
const list = candidates.map((c, i) => {
|
|
153
|
+
const name = c.route_name || c.name || `Agent ${i + 1}`;
|
|
154
|
+
const bid = c.bid_price != null ? `$${c.bid_price}` : 'N/A';
|
|
155
|
+
const rep = c.reputation_score != null ? c.reputation_score.toFixed(2) : 'unranked';
|
|
156
|
+
const cat = c.category || 'unknown';
|
|
157
|
+
return `${i + 1}. **${name}** — bid: ${bid}, reputation: ${rep}, category: ${cat}`;
|
|
158
|
+
}).join('\n');
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
content: [{
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: `Found ${candidates.length} candidate(s):\n\n${list}\n\nUse 638labs_route with a route_name to call your preferred agent.`
|
|
164
|
+
}]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Fallback: return raw result if structure is different
|
|
169
|
+
return {
|
|
170
|
+
content: [{
|
|
171
|
+
type: 'text',
|
|
172
|
+
text: `Recommendations:\n\n${JSON.stringify(result, null, 2)}`
|
|
173
|
+
}]
|
|
174
|
+
};
|
|
175
|
+
} catch (err) {
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: 'text', text: `Recommend error: ${err.message}` }],
|
|
178
|
+
isError: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// ========================
|
|
185
|
+
// Tool 3: Route (Direct)
|
|
186
|
+
// ========================
|
|
187
|
+
server.tool(
|
|
188
|
+
'638labs_route',
|
|
189
|
+
'Send a request directly to a specific AI agent by name. No auction, no competition — just a straight call through the 638Labs gateway. Use this when you already know which agent you want (e.g., from a previous recommendation or discovery).',
|
|
190
|
+
{
|
|
191
|
+
route_name: z.string().describe('The agent route name (e.g., "stolabs/prod-01" or "your-org/agent-v2")'),
|
|
192
|
+
prompt: z.string().describe('The prompt or message to send to the agent'),
|
|
193
|
+
model: z.string().optional().describe('Optional model override for the target endpoint'),
|
|
194
|
+
provider_api_key: z.string().optional().describe('Optional API key for external providers (OpenAI, Cohere, etc.)'),
|
|
195
|
+
},
|
|
196
|
+
async ({ route_name, prompt, model, provider_api_key }) => {
|
|
197
|
+
try {
|
|
198
|
+
const payload = {
|
|
199
|
+
model: model || 'default',
|
|
200
|
+
messages: [{ role: 'user', content: prompt }],
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const result = await gateway.routeRequest(route_name, payload, provider_api_key);
|
|
204
|
+
|
|
205
|
+
const responseText = result?.choices?.[0]?.message?.content
|
|
206
|
+
|| result?.message
|
|
207
|
+
|| JSON.stringify(result);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
content: [{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: `[via ${route_name}]\n\n${responseText}`
|
|
213
|
+
}]
|
|
214
|
+
};
|
|
215
|
+
} catch (err) {
|
|
216
|
+
return {
|
|
217
|
+
content: [{ type: 'text', text: `Error routing to ${route_name}: ${err.message}` }],
|
|
218
|
+
isError: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// ========================
|
|
225
|
+
// Tool 4: Discover
|
|
226
|
+
// ========================
|
|
227
|
+
server.tool(
|
|
228
|
+
'638labs_discover',
|
|
229
|
+
'Browse the 638Labs AI agent registry. Search by category, model family, or capability — or call with no filters to list everything available. Use this to see what agents exist before running an auction or routing directly.',
|
|
230
|
+
{
|
|
231
|
+
category: z.string().optional().describe('Filter by category (e.g., "chat", "summarization", "code", "data", "translation")'),
|
|
232
|
+
model_family: z.string().optional().describe('Filter by model family (e.g., "gpt", "claude", "cohere", "llama")'),
|
|
233
|
+
route_type: z.string().optional().describe('Filter by type: "model", "agent", or "datasource"'),
|
|
234
|
+
query: z.string().optional().describe('Free text search term to match against agent names'),
|
|
235
|
+
},
|
|
236
|
+
async ({ category, model_family, route_type, query }) => {
|
|
237
|
+
try {
|
|
238
|
+
const hasFilters = category || model_family || route_type || query;
|
|
239
|
+
|
|
240
|
+
// No filters = list all active endpoints
|
|
241
|
+
const endpoints = hasFilters
|
|
242
|
+
? await registry.searchEndpoints({ category, model_family, route_type, query })
|
|
243
|
+
: await registry.listEndpoints();
|
|
244
|
+
|
|
245
|
+
if (endpoints.length === 0) {
|
|
246
|
+
return {
|
|
247
|
+
content: [{
|
|
248
|
+
type: 'text',
|
|
249
|
+
text: hasFilters
|
|
250
|
+
? 'No matching agents found. Try broader filters or call with no parameters to see everything.'
|
|
251
|
+
: 'No active endpoints found in the registry.'
|
|
252
|
+
}]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const results = endpoints.map(ep => ({
|
|
257
|
+
name: ep.name,
|
|
258
|
+
route_name: ep.route_name,
|
|
259
|
+
route_type: ep.route_type,
|
|
260
|
+
category: ep.agent?.category,
|
|
261
|
+
model_family: ep.agent?.model_family,
|
|
262
|
+
model_flavour: ep.agent?.model_flavour,
|
|
263
|
+
availability: ep.agent?.availability,
|
|
264
|
+
online: ep.agent?.online,
|
|
265
|
+
quality_score: ep.agent?.quality_score,
|
|
266
|
+
auction_enabled: ep.auction?.enabled,
|
|
267
|
+
bid_strategy: ep.auction?.bid_strategy,
|
|
268
|
+
price_range: ep.auction?.enabled
|
|
269
|
+
? `${ep.auction.price_min}-${ep.auction.price_max} ${ep.auction.currency}/${ep.auction.unit}`
|
|
270
|
+
: 'N/A',
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
content: [{
|
|
275
|
+
type: 'text',
|
|
276
|
+
text: `638Labs Registry — ${results.length} agent(s):\n\n${JSON.stringify(results, null, 2)}`
|
|
277
|
+
}]
|
|
278
|
+
};
|
|
279
|
+
} catch (err) {
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: 'text', text: `Error searching registry: ${err.message}` }],
|
|
282
|
+
isError: true,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// ========================
|
|
289
|
+
// Start
|
|
290
|
+
// ========================
|
|
291
|
+
async function main() {
|
|
292
|
+
if (MODE === 'http') {
|
|
293
|
+
const app = express();
|
|
294
|
+
app.use(express.json());
|
|
295
|
+
|
|
296
|
+
const transports = {};
|
|
297
|
+
|
|
298
|
+
// Streamable HTTP endpoint — all MCP communication on /mcp
|
|
299
|
+
app.post('/mcp', async (req, res) => {
|
|
300
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
let transport;
|
|
304
|
+
|
|
305
|
+
if (sessionId && transports[sessionId]) {
|
|
306
|
+
transport = transports[sessionId];
|
|
307
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
308
|
+
transport = new StreamableHTTPServerTransport({
|
|
309
|
+
sessionIdGenerator: () => randomUUID(),
|
|
310
|
+
onsessioninitialized: (sid) => {
|
|
311
|
+
transports[sid] = transport;
|
|
312
|
+
console.error(`[638labs-mcp] Session initialized: ${sid}`);
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
transport.onclose = () => {
|
|
317
|
+
const sid = transport.sessionId;
|
|
318
|
+
if (sid && transports[sid]) {
|
|
319
|
+
console.error(`[638labs-mcp] Session closed: ${sid}`);
|
|
320
|
+
delete transports[sid];
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
await server.connect(transport);
|
|
325
|
+
await transport.handleRequest(req, res, req.body);
|
|
326
|
+
return;
|
|
327
|
+
} else {
|
|
328
|
+
res.status(400).json({
|
|
329
|
+
jsonrpc: '2.0',
|
|
330
|
+
error: { code: -32000, message: 'Bad Request: No valid session ID' },
|
|
331
|
+
id: null,
|
|
332
|
+
});
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await transport.handleRequest(req, res, req.body);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.error('[638labs-mcp] Error handling request:', err);
|
|
339
|
+
if (!res.headersSent) {
|
|
340
|
+
res.status(500).json({
|
|
341
|
+
jsonrpc: '2.0',
|
|
342
|
+
error: { code: -32603, message: 'Internal server error' },
|
|
343
|
+
id: null,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// GET /mcp — SSE stream for server-initiated messages
|
|
350
|
+
app.get('/mcp', async (req, res) => {
|
|
351
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
352
|
+
if (!sessionId || !transports[sessionId]) {
|
|
353
|
+
res.status(400).send('Invalid or missing session ID');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
await transports[sessionId].handleRequest(req, res);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// DELETE /mcp — session termination
|
|
360
|
+
app.delete('/mcp', async (req, res) => {
|
|
361
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
362
|
+
if (!sessionId || !transports[sessionId]) {
|
|
363
|
+
res.status(400).send('Invalid or missing session ID');
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
await transports[sessionId].handleRequest(req, res);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
app.listen(HTTP_PORT, () => {
|
|
370
|
+
console.error(`[638labs-mcp] Streamable HTTP server listening on http://localhost:${HTTP_PORT}`);
|
|
371
|
+
console.error(`[638labs-mcp] Endpoint: POST/GET/DELETE http://localhost:${HTTP_PORT}/mcp`);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
process.on('SIGINT', async () => {
|
|
375
|
+
for (const id in transports) {
|
|
376
|
+
await transports[id].close();
|
|
377
|
+
}
|
|
378
|
+
process.exit(0);
|
|
379
|
+
});
|
|
380
|
+
} else {
|
|
381
|
+
const transport = new StdioServerTransport();
|
|
382
|
+
await server.connect(transport);
|
|
383
|
+
console.error('[638labs-mcp] Server running on stdio — 4 tools loaded');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
main().catch((err) => {
|
|
388
|
+
console.error('[638labs-mcp] Fatal error:', err);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
});
|