@tokenlabai/mcp-server 0.3.0 → 0.4.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/README.md +74 -19
- package/contract/mcp-overlay.json +212 -0
- package/contract/openapi.json +12782 -0
- package/generated/tools.json +7230 -0
- package/llms-install.md +60 -0
- package/package.json +15 -3
- package/scripts/generate-contract.mjs +276 -0
- package/scripts/sync-contract.mjs +27 -0
- package/src/index.js +224 -328
- package/.github/workflows/publish-npm.yml +0 -53
- package/server.json +0 -42
- package/test/mcp-tools.test.js +0 -157
package/server.json
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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": "TokenLab MCP server for model discovery, pricing, OpenAI-compatible chat, and native endpoints.",
|
|
6
|
-
"repository": {
|
|
7
|
-
"url": "https://github.com/hedging8563/tokenlab-mcp-server",
|
|
8
|
-
"source": "github"
|
|
9
|
-
},
|
|
10
|
-
"version": "0.3.0",
|
|
11
|
-
"packages": [
|
|
12
|
-
{
|
|
13
|
-
"registryType": "npm",
|
|
14
|
-
"identifier": "@tokenlabai/mcp-server",
|
|
15
|
-
"version": "0.3.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_chat_completion, create_response, create_anthropic_message, and create_gemini_content.",
|
|
29
|
-
"isRequired": false,
|
|
30
|
-
"format": "string",
|
|
31
|
-
"isSecret": true
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"name": "TOKENLAB_REQUEST_TIMEOUT_MS",
|
|
35
|
-
"description": "Optional request timeout in milliseconds. Defaults to 30000.",
|
|
36
|
-
"isRequired": false,
|
|
37
|
-
"format": "number"
|
|
38
|
-
}
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
]
|
|
42
|
-
}
|
package/test/mcp-tools.test.js
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { stat } from "node:fs/promises";
|
|
3
|
-
import { createServer } from "node:http";
|
|
4
|
-
import test from "node:test";
|
|
5
|
-
|
|
6
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
8
|
-
|
|
9
|
-
async function startMockApi(t) {
|
|
10
|
-
const requests = [];
|
|
11
|
-
const server = createServer(async (request, response) => {
|
|
12
|
-
let body = "";
|
|
13
|
-
for await (const chunk of request) body += chunk;
|
|
14
|
-
|
|
15
|
-
requests.push({
|
|
16
|
-
method: request.method,
|
|
17
|
-
url: request.url,
|
|
18
|
-
authorization: request.headers.authorization,
|
|
19
|
-
body: body ? JSON.parse(body) : undefined
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
response.writeHead(200, { "Content-Type": "application/json" });
|
|
23
|
-
response.end(JSON.stringify({ ok: true }));
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
27
|
-
t.after(() => new Promise((resolve) => server.close(resolve)));
|
|
28
|
-
|
|
29
|
-
const address = server.address();
|
|
30
|
-
return {
|
|
31
|
-
baseUrl: `http://127.0.0.1:${address.port}`,
|
|
32
|
-
requests
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function startMcpClient(t, env = {}) {
|
|
37
|
-
const transport = new StdioClientTransport({
|
|
38
|
-
command: process.execPath,
|
|
39
|
-
args: ["src/index.js"],
|
|
40
|
-
cwd: process.cwd(),
|
|
41
|
-
env: { ...process.env, ...env },
|
|
42
|
-
stderr: "pipe"
|
|
43
|
-
});
|
|
44
|
-
const client = new Client({ name: "tokenlab-mcp-test", version: "0.0.0" });
|
|
45
|
-
|
|
46
|
-
await client.connect(transport);
|
|
47
|
-
t.after(() => client.close());
|
|
48
|
-
return client;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
test("advertises all catalog, OpenAI-compatible, and native endpoint tools", async (t) => {
|
|
52
|
-
const client = await startMcpClient(t);
|
|
53
|
-
const { tools } = await client.listTools();
|
|
54
|
-
const byName = Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
55
|
-
|
|
56
|
-
assert.deepEqual(Object.keys(byName).sort(), [
|
|
57
|
-
"compare_models",
|
|
58
|
-
"create_anthropic_message",
|
|
59
|
-
"create_chat_completion",
|
|
60
|
-
"create_gemini_content",
|
|
61
|
-
"create_response",
|
|
62
|
-
"get_api_overview",
|
|
63
|
-
"get_model",
|
|
64
|
-
"get_model_pricing",
|
|
65
|
-
"list_models"
|
|
66
|
-
]);
|
|
67
|
-
assert.equal(byName.create_chat_completion.inputSchema.properties.messages.type, "array");
|
|
68
|
-
assert.equal(byName.create_chat_completion.inputSchema.properties.stream, undefined);
|
|
69
|
-
assert.ok(byName.create_response.inputSchema.properties.input.anyOf);
|
|
70
|
-
assert.equal(byName.create_anthropic_message.inputSchema.properties.messages.type, "array");
|
|
71
|
-
assert.equal(byName.create_gemini_content.inputSchema.properties.contents.type, "array");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("forwards native endpoint payloads without flattening their semantics", async (t) => {
|
|
75
|
-
const api = await startMockApi(t);
|
|
76
|
-
const client = await startMcpClient(t, {
|
|
77
|
-
TOKENLAB_API_BASE: api.baseUrl,
|
|
78
|
-
TOKENLAB_API_KEY: "test-key"
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
await client.callTool({
|
|
82
|
-
name: "create_response",
|
|
83
|
-
arguments: {
|
|
84
|
-
model: "gpt-5.5",
|
|
85
|
-
input: [{ role: "user", content: [{ type: "input_text", text: "Hello" }] }],
|
|
86
|
-
tools: [{ type: "function", name: "lookup" }]
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
await client.callTool({
|
|
90
|
-
name: "create_anthropic_message",
|
|
91
|
-
arguments: {
|
|
92
|
-
model: "claude-sonnet-5",
|
|
93
|
-
max_tokens: 1024,
|
|
94
|
-
messages: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
|
|
95
|
-
tools: [{ name: "lookup", input_schema: { type: "object" } }]
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
await client.callTool({
|
|
99
|
-
name: "create_gemini_content",
|
|
100
|
-
arguments: {
|
|
101
|
-
model: "gemini-3.5-flash",
|
|
102
|
-
contents: [{ role: "user", parts: [{ text: "Hello" }] }],
|
|
103
|
-
generationConfig: { responseMimeType: "application/json" },
|
|
104
|
-
tools: [{ functionDeclarations: [{ name: "lookup" }] }]
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
assert.equal(api.requests.length, 3);
|
|
109
|
-
for (const request of api.requests) {
|
|
110
|
-
assert.equal(request.method, "POST");
|
|
111
|
-
assert.equal(request.authorization, "Bearer test-key");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
assert.equal(api.requests[0].url, "/v1/responses");
|
|
115
|
-
assert.equal(api.requests[0].body.stream, false);
|
|
116
|
-
assert.deepEqual(api.requests[0].body.input[0].content[0], { type: "input_text", text: "Hello" });
|
|
117
|
-
assert.equal(api.requests[1].url, "/v1/messages");
|
|
118
|
-
assert.equal(api.requests[1].body.stream, false);
|
|
119
|
-
assert.equal(api.requests[1].body.messages[0].content[0].type, "text");
|
|
120
|
-
assert.equal(api.requests[2].url, "/v1beta/models/gemini-3.5-flash:generateContent");
|
|
121
|
-
assert.equal(api.requests[2].body.generationConfig.responseMimeType, "application/json");
|
|
122
|
-
assert.equal(api.requests[2].body.tools[0].functionDeclarations[0].name, "lookup");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("keeps simple prompt shortcuts and requires auth for inference", async (t) => {
|
|
126
|
-
const api = await startMockApi(t);
|
|
127
|
-
const authenticated = await startMcpClient(t, {
|
|
128
|
-
TOKENLAB_API_BASE: api.baseUrl,
|
|
129
|
-
TOKENLAB_API_KEY: "test-key"
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
await authenticated.callTool({
|
|
133
|
-
name: "create_anthropic_message",
|
|
134
|
-
arguments: { model: "claude-sonnet-5", prompt: "Hello", max_tokens: 32 }
|
|
135
|
-
});
|
|
136
|
-
await authenticated.callTool({
|
|
137
|
-
name: "create_gemini_content",
|
|
138
|
-
arguments: { model: "gemini-3.5-flash", prompt: "Hello", temperature: 0.2 }
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
assert.equal(api.requests[0].body.messages[0].content, "Hello");
|
|
142
|
-
assert.equal(api.requests[1].body.contents[0].parts[0].text, "Hello");
|
|
143
|
-
assert.equal(api.requests[1].body.generationConfig.temperature, 0.2);
|
|
144
|
-
|
|
145
|
-
const unauthenticated = await startMcpClient(t, { TOKENLAB_API_BASE: api.baseUrl });
|
|
146
|
-
const result = await unauthenticated.callTool({
|
|
147
|
-
name: "create_response",
|
|
148
|
-
arguments: { model: "gpt-5.5", input: "Hello" }
|
|
149
|
-
});
|
|
150
|
-
assert.equal(result.isError, true);
|
|
151
|
-
assert.match(result.content[0].text, /TOKENLAB_API_KEY is required/);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("ships an executable npm binary", async () => {
|
|
155
|
-
const { mode } = await stat(new URL("../src/index.js", import.meta.url));
|
|
156
|
-
assert.notEqual(mode & 0o111, 0);
|
|
157
|
-
});
|