@tokenlabai/mcp-server 0.2.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/LICENSE +21 -0
- package/README.md +75 -0
- package/package.json +36 -0
- package/server.json +36 -0
- package/src/index.js +255 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TokenLab
|
|
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,75 @@
|
|
|
1
|
+
# TokenLab MCP Server
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server for TokenLab public model discovery, pricing, native endpoint guidance, and optional inference helpers.
|
|
4
|
+
|
|
5
|
+
It exposes public catalog tools for agents that need to choose models, inspect supported request formats, or compare pricing before calling TokenLab APIs. Optional inference tools require `TOKENLAB_API_KEY`.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
- `list_models` - List public TokenLab models, optionally filtered by `recommended_for`.
|
|
10
|
+
- `get_model` - Fetch public model details for one model ID.
|
|
11
|
+
- `get_model_pricing` - Fetch pricing details for one model ID.
|
|
12
|
+
- `compare_models` - Compare details and pricing for several model IDs.
|
|
13
|
+
- `get_api_overview` - Fetch the agent-readable `llms.txt` overview.
|
|
14
|
+
- `create_response` - Call TokenLab Responses API. Requires `TOKENLAB_API_KEY`.
|
|
15
|
+
- `create_anthropic_message` - Call TokenLab Anthropic Messages API. Requires `TOKENLAB_API_KEY`.
|
|
16
|
+
- `create_gemini_content` - Call TokenLab Gemini generateContent API. Requires `TOKENLAB_API_KEY`.
|
|
17
|
+
|
|
18
|
+
## Run
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install
|
|
22
|
+
npm start
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Install from npm:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx -y @tokenlabai/mcp-server
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Claude Desktop style config:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"tokenlab-model-catalog": {
|
|
37
|
+
"command": "node",
|
|
38
|
+
"args": ["/absolute/path/to/tokenlab-mcp-server/src/index.js"],
|
|
39
|
+
"env": {
|
|
40
|
+
"TOKENLAB_API_BASE": "https://api.tokenlab.sh"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
No TokenLab API key is required for the public catalog tools. Set `TOKENLAB_API_KEY` only when you want the inference helper tools to call paid TokenLab APIs.
|
|
48
|
+
|
|
49
|
+
## Environment
|
|
50
|
+
|
|
51
|
+
- `TOKENLAB_API_BASE`: optional, defaults to `https://api.tokenlab.sh`
|
|
52
|
+
- `TOKENLAB_API_KEY`: optional; required only for `create_response`, `create_anthropic_message`, and `create_gemini_content`
|
|
53
|
+
|
|
54
|
+
## MCP Registry Metadata
|
|
55
|
+
|
|
56
|
+
This repository includes `server.json` for the official MCP Registry.
|
|
57
|
+
|
|
58
|
+
The package uses:
|
|
59
|
+
|
|
60
|
+
- npm package: `@tokenlabai/mcp-server`
|
|
61
|
+
- MCP registry name: `io.github.hedging8563/tokenlab`
|
|
62
|
+
- `package.json.mcpName`: `io.github.hedging8563/tokenlab`
|
|
63
|
+
|
|
64
|
+
Publish order:
|
|
65
|
+
|
|
66
|
+
1. Publish `@tokenlabai/mcp-server` to npm.
|
|
67
|
+
2. Authenticate with `mcp-publisher login github`.
|
|
68
|
+
3. Run `mcp-publisher publish`.
|
|
69
|
+
|
|
70
|
+
## Links
|
|
71
|
+
|
|
72
|
+
- Docs: https://docs.tokenlab.sh
|
|
73
|
+
- OpenAPI: https://docs.tokenlab.sh/openapi.json
|
|
74
|
+
- Model catalog: https://api.tokenlab.sh/v1/models
|
|
75
|
+
- Skills: https://github.com/hedging8563/tokenlab-skills
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tokenlabai/mcp-server",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for TokenLab model discovery, pricing, native endpoint guidance, and optional inference helpers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"mcpName": "io.github.hedging8563/tokenlab",
|
|
7
|
+
"bin": {
|
|
8
|
+
"tokenlab-mcp-server": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node ./src/index.js",
|
|
12
|
+
"test": "node --check ./src/index.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"tokenlab",
|
|
18
|
+
"ai-api",
|
|
19
|
+
"llm-gateway",
|
|
20
|
+
"model-routing"
|
|
21
|
+
],
|
|
22
|
+
"author": "TokenLab",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
+
"zod": "^4.2.0"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/hedging8563/tokenlab-mcp-server.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://docs.tokenlab.sh"
|
|
36
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.hedging8563/tokenlab",
|
|
4
|
+
"title": "TokenLab",
|
|
5
|
+
"description": "MCP server for TokenLab model discovery, pricing, native endpoint guidance, and optional inference helpers.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/hedging8563/tokenlab-mcp-server",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.2.0",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"identifier": "@tokenlabai/mcp-server",
|
|
15
|
+
"version": "0.2.0",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "stdio"
|
|
18
|
+
},
|
|
19
|
+
"environmentVariables": [
|
|
20
|
+
{
|
|
21
|
+
"name": "TOKENLAB_API_BASE",
|
|
22
|
+
"description": "Optional TokenLab API base URL. Defaults to https://api.tokenlab.sh.",
|
|
23
|
+
"isRequired": false,
|
|
24
|
+
"format": "string"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "TOKENLAB_API_KEY",
|
|
28
|
+
"description": "Optional TokenLab API key. Required only for inference tools such as create_response, create_anthropic_message, and create_gemini_content.",
|
|
29
|
+
"isRequired": false,
|
|
30
|
+
"format": "string",
|
|
31
|
+
"isSecret": true
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
const VERSION = "0.2.0";
|
|
8
|
+
const API_BASE = (process.env.TOKENLAB_API_BASE || "https://api.tokenlab.sh").replace(/\/$/, "");
|
|
9
|
+
const API_KEY = process.env.TOKENLAB_API_KEY || "";
|
|
10
|
+
|
|
11
|
+
const scenes = [
|
|
12
|
+
"image",
|
|
13
|
+
"video",
|
|
14
|
+
"music",
|
|
15
|
+
"3d",
|
|
16
|
+
"tts",
|
|
17
|
+
"stt",
|
|
18
|
+
"embedding",
|
|
19
|
+
"rerank",
|
|
20
|
+
"translation"
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const server = new McpServer({
|
|
24
|
+
name: "tokenlab",
|
|
25
|
+
version: VERSION
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
async function fetchJson(path, options = {}) {
|
|
29
|
+
const headers = {
|
|
30
|
+
Accept: "application/json",
|
|
31
|
+
"User-Agent": `tokenlab-mcp-server/${VERSION}`
|
|
32
|
+
};
|
|
33
|
+
if (options.body) {
|
|
34
|
+
headers["Content-Type"] = "application/json";
|
|
35
|
+
}
|
|
36
|
+
if (options.auth) {
|
|
37
|
+
if (!API_KEY) {
|
|
38
|
+
throw new Error("TOKENLAB_API_KEY is required for TokenLab inference tools.");
|
|
39
|
+
}
|
|
40
|
+
headers.Authorization = `Bearer ${API_KEY}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const response = await fetch(`${API_BASE}${path}`, {
|
|
44
|
+
method: options.method || "GET",
|
|
45
|
+
headers,
|
|
46
|
+
body: options.body ? JSON.stringify(options.body) : undefined
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const text = await response.text();
|
|
50
|
+
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
throw new Error(`TokenLab request failed: ${response.status} ${response.statusText}\n${text}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return JSON.parse(text);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function textResult(value) {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function compactModelDetails(details, pricing) {
|
|
70
|
+
return {
|
|
71
|
+
id: details.id || details.model || details.name,
|
|
72
|
+
object: details.object,
|
|
73
|
+
owned_by: details.owned_by,
|
|
74
|
+
request_endpoint: details.request_endpoint,
|
|
75
|
+
request_shape_mode: details.request_shape_mode,
|
|
76
|
+
supported_operations: details.supported_operations,
|
|
77
|
+
supported_parameters: details.supported_parameters,
|
|
78
|
+
recommended_request: details.recommended_request,
|
|
79
|
+
pricing
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
server.tool(
|
|
84
|
+
"list_models",
|
|
85
|
+
"List public TokenLab models, optionally filtered by recommended task.",
|
|
86
|
+
{
|
|
87
|
+
recommended_for: z.enum(scenes).optional().describe("Optional task filter such as image, video, embedding, or rerank."),
|
|
88
|
+
limit: z.number().int().min(1).max(100).default(25).describe("Maximum number of models to return.")
|
|
89
|
+
},
|
|
90
|
+
async ({ recommended_for, limit }) => {
|
|
91
|
+
const query = recommended_for ? `?recommended_for=${encodeURIComponent(recommended_for)}` : "";
|
|
92
|
+
const data = await fetchJson(`/v1/models${query}`);
|
|
93
|
+
const models = Array.isArray(data.data) ? data.data.slice(0, limit) : [];
|
|
94
|
+
|
|
95
|
+
return textResult({
|
|
96
|
+
object: data.object,
|
|
97
|
+
count: Array.isArray(data.data) ? data.data.length : 0,
|
|
98
|
+
returned: models.length,
|
|
99
|
+
models
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
server.tool(
|
|
105
|
+
"get_model",
|
|
106
|
+
"Fetch public TokenLab model details for one model ID.",
|
|
107
|
+
{
|
|
108
|
+
model: z.string().min(1).describe("Public TokenLab model ID, for example gpt-5.5 or gemini-3.5-flash.")
|
|
109
|
+
},
|
|
110
|
+
async ({ model }) => {
|
|
111
|
+
return textResult(await fetchJson(`/v1/models/${encodeURIComponent(model)}`));
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
server.tool(
|
|
116
|
+
"get_model_pricing",
|
|
117
|
+
"Fetch public TokenLab pricing details for one model ID.",
|
|
118
|
+
{
|
|
119
|
+
model: z.string().min(1).describe("Public TokenLab model ID.")
|
|
120
|
+
},
|
|
121
|
+
async ({ model }) => {
|
|
122
|
+
return textResult(await fetchJson(`/v1/models/${encodeURIComponent(model)}/pricing`));
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
server.tool(
|
|
127
|
+
"compare_models",
|
|
128
|
+
"Compare public TokenLab model details and pricing for several model IDs.",
|
|
129
|
+
{
|
|
130
|
+
models: z.array(z.string().min(1)).min(2).max(8).describe("Public TokenLab model IDs to compare."),
|
|
131
|
+
include_raw: z.boolean().default(false).describe("Return raw details and pricing payloads instead of compact summaries.")
|
|
132
|
+
},
|
|
133
|
+
async ({ models, include_raw }) => {
|
|
134
|
+
const compared = await Promise.all(models.map(async (model) => {
|
|
135
|
+
const encoded = encodeURIComponent(model);
|
|
136
|
+
const [details, pricing] = await Promise.all([
|
|
137
|
+
fetchJson(`/v1/models/${encoded}`),
|
|
138
|
+
fetchJson(`/v1/models/${encoded}/pricing`).catch((error) => ({
|
|
139
|
+
error: error.message
|
|
140
|
+
}))
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
return include_raw ? { model, details, pricing } : compactModelDetails(details, pricing);
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
return textResult({ compared });
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
server.tool(
|
|
151
|
+
"create_response",
|
|
152
|
+
"Create a TokenLab Responses API call. Requires TOKENLAB_API_KEY.",
|
|
153
|
+
{
|
|
154
|
+
model: z.string().min(1).describe("Public TokenLab model ID."),
|
|
155
|
+
input: z.string().min(1).describe("Responses API input text."),
|
|
156
|
+
instructions: z.string().optional().describe("Optional system/developer instructions."),
|
|
157
|
+
max_output_tokens: z.number().int().min(1).max(8192).optional().describe("Optional output token cap.")
|
|
158
|
+
},
|
|
159
|
+
async ({ model, input, instructions, max_output_tokens }) => {
|
|
160
|
+
return textResult(await fetchJson("/v1/responses", {
|
|
161
|
+
method: "POST",
|
|
162
|
+
auth: true,
|
|
163
|
+
body: {
|
|
164
|
+
model,
|
|
165
|
+
input,
|
|
166
|
+
...(instructions ? { instructions } : {}),
|
|
167
|
+
...(max_output_tokens ? { max_output_tokens } : {})
|
|
168
|
+
}
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
server.tool(
|
|
174
|
+
"create_anthropic_message",
|
|
175
|
+
"Create a TokenLab Anthropic Messages call. Requires TOKENLAB_API_KEY.",
|
|
176
|
+
{
|
|
177
|
+
model: z.string().min(1).describe("Public TokenLab Claude-compatible model ID."),
|
|
178
|
+
prompt: z.string().min(1).describe("User prompt text."),
|
|
179
|
+
system: z.string().optional().describe("Optional system prompt."),
|
|
180
|
+
max_tokens: z.number().int().min(1).max(8192).default(512).describe("Maximum output tokens.")
|
|
181
|
+
},
|
|
182
|
+
async ({ model, prompt, system, max_tokens }) => {
|
|
183
|
+
return textResult(await fetchJson("/v1/messages", {
|
|
184
|
+
method: "POST",
|
|
185
|
+
auth: true,
|
|
186
|
+
body: {
|
|
187
|
+
model,
|
|
188
|
+
max_tokens,
|
|
189
|
+
...(system ? { system } : {}),
|
|
190
|
+
messages: [
|
|
191
|
+
{
|
|
192
|
+
role: "user",
|
|
193
|
+
content: prompt
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
server.tool(
|
|
202
|
+
"create_gemini_content",
|
|
203
|
+
"Create a TokenLab Gemini generateContent call. Requires TOKENLAB_API_KEY.",
|
|
204
|
+
{
|
|
205
|
+
model: z.string().min(1).describe("Public TokenLab Gemini-compatible model ID."),
|
|
206
|
+
prompt: z.string().min(1).describe("User prompt text."),
|
|
207
|
+
temperature: z.number().min(0).max(2).optional().describe("Optional Gemini generation temperature.")
|
|
208
|
+
},
|
|
209
|
+
async ({ model, prompt, temperature }) => {
|
|
210
|
+
return textResult(await fetchJson(`/v1beta/models/${encodeURIComponent(model)}:generateContent`, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
auth: true,
|
|
213
|
+
body: {
|
|
214
|
+
contents: [
|
|
215
|
+
{
|
|
216
|
+
role: "user",
|
|
217
|
+
parts: [
|
|
218
|
+
{
|
|
219
|
+
text: prompt
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
],
|
|
224
|
+
...(temperature === undefined ? {} : {
|
|
225
|
+
generationConfig: {
|
|
226
|
+
temperature
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
server.tool(
|
|
235
|
+
"get_api_overview",
|
|
236
|
+
"Fetch TokenLab's agent-readable API overview.",
|
|
237
|
+
{},
|
|
238
|
+
async () => {
|
|
239
|
+
const response = await fetch(`${API_BASE}/llms.txt`, {
|
|
240
|
+
headers: {
|
|
241
|
+
Accept: "text/plain",
|
|
242
|
+
"User-Agent": `tokenlab-mcp-server/${VERSION}`
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (!response.ok) {
|
|
247
|
+
throw new Error(`TokenLab overview request failed: ${response.status} ${response.statusText}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return textResult(await response.text());
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const transport = new StdioServerTransport();
|
|
255
|
+
await server.connect(transport);
|