@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 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
+ });