@goke/mcp 0.0.5 → 0.0.6
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 +56 -0
- package/dist/src/auth.d.ts.map +1 -0
- package/dist/src/cli-to-mcp.d.ts +17 -0
- package/dist/src/cli-to-mcp.d.ts.map +1 -0
- package/dist/src/cli-to-mcp.js +380 -0
- package/dist/{index.d.ts → src/index.d.ts} +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/{index.js → src/index.js} +2 -1
- package/dist/src/local-callback-server.d.ts.map +1 -0
- package/dist/src/oauth-provider.d.ts.map +1 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/test/add-cli-tools-to-mcp.test.d.ts +5 -0
- package/dist/test/add-cli-tools-to-mcp.test.d.ts.map +1 -0
- package/dist/test/add-cli-tools-to-mcp.test.js +399 -0
- package/package.json +5 -2
- package/src/cli-to-mcp.ts +500 -0
- package/src/index.ts +3 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/local-callback-server.d.ts.map +0 -1
- package/dist/oauth-provider.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- /package/dist/{auth.d.ts → src/auth.d.ts} +0 -0
- /package/dist/{auth.js → src/auth.js} +0 -0
- /package/dist/{local-callback-server.d.ts → src/local-callback-server.d.ts} +0 -0
- /package/dist/{local-callback-server.js → src/local-callback-server.js} +0 -0
- /package/dist/{oauth-provider.d.ts → src/oauth-provider.d.ts} +0 -0
- /package/dist/{oauth-provider.js → src/oauth-provider.js} +0 -0
- /package/dist/{types.d.ts → src/types.d.ts} +0 -0
- /package/dist/{types.js → src/types.js} +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI to MCP adapter.
|
|
3
|
+
*
|
|
4
|
+
* Exposes goke commands as MCP tools on either a low-level Server
|
|
5
|
+
* or a high-level McpServer by mounting tools/list + tools/call handlers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ErrorCode,
|
|
13
|
+
ListToolsRequestSchema,
|
|
14
|
+
McpError,
|
|
15
|
+
type CallToolResult,
|
|
16
|
+
type Tool,
|
|
17
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
18
|
+
import { coerceBySchema, extractJsonSchema, type Command, type Goke, type StandardJSONSchemaV1 } from "goke";
|
|
19
|
+
|
|
20
|
+
const CLI_TO_MCP_STATE = Symbol.for("@goke/mcp/cli-to-mcp-state");
|
|
21
|
+
|
|
22
|
+
interface CommandArgLike {
|
|
23
|
+
required: boolean;
|
|
24
|
+
value: string;
|
|
25
|
+
variadic: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface OptionLike {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
default?: unknown;
|
|
32
|
+
required?: boolean;
|
|
33
|
+
isBoolean?: boolean;
|
|
34
|
+
schema?: StandardJSONSchemaV1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface OptionBinding {
|
|
38
|
+
name: string;
|
|
39
|
+
defaultValue?: unknown;
|
|
40
|
+
jsonSchema?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface CliToolBinding {
|
|
44
|
+
tool: Tool;
|
|
45
|
+
command: Command;
|
|
46
|
+
positionalArgs: CommandArgLike[];
|
|
47
|
+
options: OptionBinding[];
|
|
48
|
+
requiredNames: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CliToMcpState {
|
|
52
|
+
toolsByName: Map<string, CliToolBinding>;
|
|
53
|
+
commandToToolName: Map<string, string>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type AnyRequestHandler = (request: unknown, extra: unknown) => unknown | Promise<unknown>;
|
|
57
|
+
|
|
58
|
+
function isMountableCommand(command: Command, commandFilter?: (commandName: string) => boolean): boolean {
|
|
59
|
+
if (!command.commandAction) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (command.name === "") {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (commandFilter && !commandFilter(command.name)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AddCliToolsToMcpOptions {
|
|
75
|
+
cli: Goke;
|
|
76
|
+
server: Server | McpServer;
|
|
77
|
+
commandFilter?: (commandName: string) => boolean;
|
|
78
|
+
sanitizeToolName?: (commandName: string) => string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isMcpServer(value: Server | McpServer): value is McpServer {
|
|
82
|
+
return "server" in value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveServer(value: Server | McpServer): Server {
|
|
86
|
+
if (isMcpServer(value)) {
|
|
87
|
+
return value.server;
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getToolCallArguments(args: Record<string, unknown>, name: string): unknown {
|
|
93
|
+
if (name in args) {
|
|
94
|
+
return args[name];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const parts = name.split(".");
|
|
98
|
+
let current: unknown = args;
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
if (current != null && typeof current === "object" && part in (current as Record<string, unknown>)) {
|
|
101
|
+
current = (current as Record<string, unknown>)[part];
|
|
102
|
+
} else {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return current;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function setDotProp(target: Record<string, unknown>, keys: string[], value: unknown): void {
|
|
110
|
+
let current: Record<string, unknown> = target;
|
|
111
|
+
|
|
112
|
+
for (let i = 0; i < keys.length; i++) {
|
|
113
|
+
const key = keys[i];
|
|
114
|
+
if (i === keys.length - 1) {
|
|
115
|
+
current[key] = value;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const existing = current[key];
|
|
120
|
+
if (existing != null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
121
|
+
current = existing as Record<string, unknown>;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const next: Record<string, unknown> = {};
|
|
126
|
+
current[key] = next;
|
|
127
|
+
current = next;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function defaultSanitizeToolName(commandName: string): string {
|
|
132
|
+
let name = commandName.trim();
|
|
133
|
+
name = name.replace(/\s+/g, "_");
|
|
134
|
+
name = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
135
|
+
name = name.replace(/_+/g, "_");
|
|
136
|
+
name = name.replace(/^[._-]+|[._-]+$/g, "");
|
|
137
|
+
|
|
138
|
+
if (!name) {
|
|
139
|
+
name = "tool";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (name.length > 128) {
|
|
143
|
+
name = name.slice(0, 128);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return name;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function uniqueToolName(baseName: string, usedNames: Set<string>): string {
|
|
150
|
+
if (!usedNames.has(baseName)) {
|
|
151
|
+
return baseName;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (let i = 2; i < 10_000; i++) {
|
|
155
|
+
const suffix = `_${i}`;
|
|
156
|
+
const prefixMax = 128 - suffix.length;
|
|
157
|
+
const candidate = `${baseName.slice(0, Math.max(1, prefixMax))}${suffix}`;
|
|
158
|
+
if (!usedNames.has(candidate)) {
|
|
159
|
+
return candidate;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
throw new Error(`Unable to generate a unique MCP tool name for ${baseName}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeOptionSchema(option: OptionLike): { schema: Record<string, unknown>; jsonSchema?: Record<string, unknown> } {
|
|
167
|
+
const schemaFromOption = option.schema ? extractJsonSchema(option.schema) : undefined;
|
|
168
|
+
const schema: Record<string, unknown> = schemaFromOption ? { ...schemaFromOption } : {
|
|
169
|
+
type: option.isBoolean ? "boolean" : "string",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (typeof schema.description !== "string" && option.description) {
|
|
173
|
+
schema.description = option.description;
|
|
174
|
+
}
|
|
175
|
+
if (schema.default === undefined && option.default !== undefined) {
|
|
176
|
+
schema.default = option.default;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { schema, jsonSchema: schemaFromOption };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function commandDescription(command: Command): string {
|
|
183
|
+
const description = command.description.trim();
|
|
184
|
+
if (description) {
|
|
185
|
+
return description;
|
|
186
|
+
}
|
|
187
|
+
return `Run CLI command ${command.name}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function formatTextResult(value: unknown): string {
|
|
191
|
+
if (typeof value === "string") {
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
if (value == null) {
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
198
|
+
return String(value);
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
return JSON.stringify(value, null, 2);
|
|
202
|
+
} catch {
|
|
203
|
+
return String(value);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function toCallToolResult(value: unknown): CallToolResult {
|
|
208
|
+
if (value && typeof value === "object" && "content" in value) {
|
|
209
|
+
return value as CallToolResult;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
content: [{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: formatTextResult(value),
|
|
216
|
+
}],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getExistingRequestHandler(server: Server, method: string): AnyRequestHandler | undefined {
|
|
221
|
+
const handlerMap = (server as unknown as { _requestHandlers?: unknown })._requestHandlers;
|
|
222
|
+
if (!(handlerMap instanceof Map)) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return (handlerMap as Map<string, AnyRequestHandler>).get(method);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isToolNotFoundError(error: unknown, toolName: string): boolean {
|
|
229
|
+
if (!(error instanceof McpError)) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (error.code !== ErrorCode.InvalidParams) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const message = String(error.message).toLowerCase();
|
|
238
|
+
return message.includes("tool") && message.includes("not found") && message.includes(toolName.toLowerCase());
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isToolNotFoundResult(result: unknown, toolName: string): boolean {
|
|
242
|
+
if (!result || typeof result !== "object") {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const maybe = result as { isError?: boolean; content?: Array<{ type?: string; text?: string }> };
|
|
247
|
+
if (!maybe.isError || !Array.isArray(maybe.content)) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const textBlock = maybe.content.find((entry) => entry?.type === "text");
|
|
252
|
+
const text = String(textBlock?.text ?? "").toLowerCase();
|
|
253
|
+
return text.includes("tool") && text.includes("not found") && text.includes(toolName.toLowerCase());
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function runCliTool(binding: CliToolBinding, argumentsObject: Record<string, unknown>): Promise<CallToolResult> {
|
|
257
|
+
for (const requiredName of binding.requiredNames) {
|
|
258
|
+
if (getToolCallArguments(argumentsObject, requiredName) === undefined) {
|
|
259
|
+
throw new McpError(ErrorCode.InvalidParams, `Missing required argument: ${requiredName}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const positionalValues: unknown[] = [];
|
|
264
|
+
for (const arg of binding.positionalArgs) {
|
|
265
|
+
const value = getToolCallArguments(argumentsObject, arg.value);
|
|
266
|
+
|
|
267
|
+
if (arg.variadic) {
|
|
268
|
+
if (value === undefined) {
|
|
269
|
+
positionalValues.push([]);
|
|
270
|
+
} else if (Array.isArray(value)) {
|
|
271
|
+
positionalValues.push(value.map((entry) => String(entry)));
|
|
272
|
+
} else {
|
|
273
|
+
positionalValues.push([String(value)]);
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
positionalValues.push(value === undefined ? undefined : String(value));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const optionsObject: Record<string, unknown> = {};
|
|
281
|
+
for (const option of binding.options) {
|
|
282
|
+
let optionValue = getToolCallArguments(argumentsObject, option.name);
|
|
283
|
+
if (optionValue === undefined && option.defaultValue !== undefined) {
|
|
284
|
+
optionValue = option.defaultValue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (optionValue !== undefined && option.jsonSchema) {
|
|
288
|
+
const isStringArray = Array.isArray(optionValue) && optionValue.every((value) => typeof value === "string");
|
|
289
|
+
const isCoercibleType = typeof optionValue === "string" || typeof optionValue === "boolean" || isStringArray;
|
|
290
|
+
if (isCoercibleType) {
|
|
291
|
+
optionValue = coerceBySchema(optionValue as string | boolean | string[], option.jsonSchema, option.name);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (optionValue !== undefined) {
|
|
296
|
+
setDotProp(optionsObject, option.name.split("."), optionValue);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const action = binding.command.commandAction;
|
|
301
|
+
if (!action) {
|
|
302
|
+
throw new McpError(ErrorCode.InvalidParams, `Command ${binding.command.name} has no action`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const result = await Promise.resolve(action(...positionalValues, optionsObject));
|
|
307
|
+
return toCallToolResult(result);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
310
|
+
return {
|
|
311
|
+
isError: true,
|
|
312
|
+
content: [{ type: "text", text: message }],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function createBinding(command: Command, toolName: string): CliToolBinding {
|
|
318
|
+
const positionalArgs = command.args as unknown as CommandArgLike[];
|
|
319
|
+
const options = command.options as unknown as OptionLike[];
|
|
320
|
+
|
|
321
|
+
const properties: Record<string, Record<string, unknown>> = {};
|
|
322
|
+
const requiredNames: string[] = [];
|
|
323
|
+
const optionBindings: OptionBinding[] = [];
|
|
324
|
+
|
|
325
|
+
for (const arg of positionalArgs) {
|
|
326
|
+
if (arg.variadic) {
|
|
327
|
+
properties[arg.value] = {
|
|
328
|
+
type: "array",
|
|
329
|
+
items: { type: "string" },
|
|
330
|
+
description: `Positional argument ${arg.value}`,
|
|
331
|
+
};
|
|
332
|
+
} else {
|
|
333
|
+
properties[arg.value] = {
|
|
334
|
+
type: "string",
|
|
335
|
+
description: `Positional argument ${arg.value}`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (arg.required) {
|
|
340
|
+
requiredNames.push(arg.value);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for (const option of options) {
|
|
345
|
+
const normalized = normalizeOptionSchema(option);
|
|
346
|
+
properties[option.name] = normalized.schema;
|
|
347
|
+
|
|
348
|
+
if (option.required) {
|
|
349
|
+
requiredNames.push(option.name);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
optionBindings.push({
|
|
353
|
+
name: option.name,
|
|
354
|
+
defaultValue: option.default,
|
|
355
|
+
jsonSchema: normalized.jsonSchema,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const inputSchema: Tool["inputSchema"] = {
|
|
360
|
+
type: "object",
|
|
361
|
+
properties,
|
|
362
|
+
...(requiredNames.length > 0 ? { required: Array.from(new Set(requiredNames)) } : {}),
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
tool: {
|
|
367
|
+
name: toolName,
|
|
368
|
+
description: commandDescription(command),
|
|
369
|
+
inputSchema,
|
|
370
|
+
},
|
|
371
|
+
command,
|
|
372
|
+
positionalArgs,
|
|
373
|
+
options: optionBindings,
|
|
374
|
+
requiredNames: Array.from(new Set(requiredNames)),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function getOrInstallState(server: Server): CliToMcpState {
|
|
379
|
+
const serverWithState = server as Server & { [CLI_TO_MCP_STATE]?: CliToMcpState };
|
|
380
|
+
const existing = serverWithState[CLI_TO_MCP_STATE];
|
|
381
|
+
if (existing) {
|
|
382
|
+
return existing;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const existingListHandler = getExistingRequestHandler(server, "tools/list");
|
|
386
|
+
const existingCallHandler = getExistingRequestHandler(server, "tools/call");
|
|
387
|
+
|
|
388
|
+
if (!existingListHandler && !existingCallHandler) {
|
|
389
|
+
server.registerCapabilities({ tools: { listChanged: true } });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const state: CliToMcpState = {
|
|
393
|
+
toolsByName: new Map(),
|
|
394
|
+
commandToToolName: new Map(),
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
398
|
+
const localTools = Array.from(state.toolsByName.values()).map((binding) => binding.tool);
|
|
399
|
+
if (!existingListHandler) {
|
|
400
|
+
return { tools: localTools };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const previousResult = await Promise.resolve(existingListHandler(request, extra)) as {
|
|
404
|
+
tools?: Tool[];
|
|
405
|
+
nextCursor?: string;
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const merged = new Map<string, Tool>();
|
|
409
|
+
for (const tool of previousResult.tools ?? []) {
|
|
410
|
+
merged.set(tool.name, tool);
|
|
411
|
+
}
|
|
412
|
+
for (const tool of localTools) {
|
|
413
|
+
if (!merged.has(tool.name)) {
|
|
414
|
+
merged.set(tool.name, tool);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
...previousResult,
|
|
420
|
+
tools: Array.from(merged.values()),
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
425
|
+
const binding = state.toolsByName.get(request.params.name);
|
|
426
|
+
const argumentsObject = request.params.arguments ?? {};
|
|
427
|
+
|
|
428
|
+
if (existingCallHandler) {
|
|
429
|
+
try {
|
|
430
|
+
const existingResult = await Promise.resolve(existingCallHandler(request, extra)) as CallToolResult;
|
|
431
|
+
if (binding && isToolNotFoundResult(existingResult, request.params.name)) {
|
|
432
|
+
return runCliTool(binding, argumentsObject);
|
|
433
|
+
}
|
|
434
|
+
return existingResult;
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (!binding || !isToolNotFoundError(error, request.params.name)) {
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (!binding) {
|
|
443
|
+
throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return runCliTool(binding, argumentsObject);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
Object.defineProperty(serverWithState, CLI_TO_MCP_STATE, {
|
|
450
|
+
value: state,
|
|
451
|
+
enumerable: false,
|
|
452
|
+
writable: false,
|
|
453
|
+
configurable: false,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
return state;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function addCliToolsToMcp(options: AddCliToolsToMcpOptions): void {
|
|
460
|
+
const { cli, commandFilter, sanitizeToolName = defaultSanitizeToolName } = options;
|
|
461
|
+
const server = resolveServer(options.server);
|
|
462
|
+
const state = getOrInstallState(server);
|
|
463
|
+
const usedNames = new Set(state.toolsByName.keys());
|
|
464
|
+
|
|
465
|
+
const activeCommandNames = new Set<string>();
|
|
466
|
+
for (const command of cli.commands) {
|
|
467
|
+
if (isMountableCommand(command, commandFilter)) {
|
|
468
|
+
activeCommandNames.add(command.name);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const [commandName, toolName] of state.commandToToolName) {
|
|
473
|
+
if (!activeCommandNames.has(commandName)) {
|
|
474
|
+
state.commandToToolName.delete(commandName);
|
|
475
|
+
state.toolsByName.delete(toolName);
|
|
476
|
+
usedNames.delete(toolName);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
for (const command of cli.commands) {
|
|
481
|
+
if (!isMountableCommand(command, commandFilter)) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const existingToolName = state.commandToToolName.get(command.name);
|
|
486
|
+
if (existingToolName) {
|
|
487
|
+
state.toolsByName.delete(existingToolName);
|
|
488
|
+
state.commandToToolName.delete(command.name);
|
|
489
|
+
usedNames.delete(existingToolName);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const baseToolName = defaultSanitizeToolName(sanitizeToolName(command.name));
|
|
493
|
+
const toolName = uniqueToolName(baseToolName, usedNames);
|
|
494
|
+
usedNames.add(toolName);
|
|
495
|
+
|
|
496
|
+
const binding = createBinding(command, toolName);
|
|
497
|
+
state.toolsByName.set(toolName, binding);
|
|
498
|
+
state.commandToToolName.set(command.name, toolName);
|
|
499
|
+
}
|
|
500
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -49,6 +49,8 @@ import yaml from "js-yaml";
|
|
|
49
49
|
import { FileOAuthProvider } from "./oauth-provider.js";
|
|
50
50
|
import { startOAuthFlow, isAuthRequiredError } from "./auth.js";
|
|
51
51
|
import type { McpOAuthConfig, McpOAuthState } from "./types.js";
|
|
52
|
+
export { addCliToolsToMcp } from "./cli-to-mcp.js";
|
|
53
|
+
export type { AddCliToolsToMcpOptions } from "./cli-to-mcp.js";
|
|
52
54
|
|
|
53
55
|
// Public exports - only types that consumers need
|
|
54
56
|
export type { Transport };
|
|
@@ -411,7 +413,7 @@ export async function addMcpCommands(options: AddMcpCommandsOptions): Promise<vo
|
|
|
411
413
|
cmd.option(optionStr, optionDesc);
|
|
412
414
|
} else {
|
|
413
415
|
// Wrap the MCP tool's JSON Schema property into a StandardJSONSchemaV1
|
|
414
|
-
//
|
|
416
|
+
// so goke can use it for type coercion.
|
|
415
417
|
// Put the enriched description into the JSON Schema so it's extracted automatically.
|
|
416
418
|
// Boolean flags with defaults also go through this path to preserve the default.
|
|
417
419
|
const enrichedSchema = { ...propSchema, description: optionDesc } as Record<string, unknown>;
|
package/dist/auth.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,eAAe,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA0BxF;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CA4E7F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAazD"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAKjC,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,YAAY,CAAC;AAGhE,YAAY,EAAE,SAAS,EAAE,CAAC;AAC1B,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC,CAAC;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,IAAI,CAAC;IACV;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAErC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAEvF;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IAEvB;;OAEG;IACH,SAAS,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAE5C;;OAEG;IACH,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,KAAK,IAAI,CAAC;CACxD;AAmID;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyMlF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"local-callback-server.d.ts","sourceRoot":"","sources":["../src/local-callback-server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA2FxE;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/C,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC,CAgFD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-provider.d.ts","sourceRoot":"","sources":["../src/oauth-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AACpF,OAAO,KAAK,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,mBAAmB,EACnB,WAAW,EACZ,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CACjD;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,mBAAmB;IAC3D,OAAO,CAAC,kBAAkB,CAAqC;IAC/D,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,qBAAqB,CAAkB;IAE/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAiC;gBAErD,OAAO,EAAE,wBAAwB;IAU7C,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;OAGG;IACH,IAAI,oBAAoB,IAAI,GAAG,GAAG,SAAS,CAE1C;IAEK,iBAAiB,IAAI,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC;IAIhE,qBAAqB,CAAC,iBAAiB,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnF,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAO/B,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3D,IAAI,cAAc,IAAI,mBAAmB,CAKxC;IAED;;;OAGG;IACH,uBAAuB,CAAC,gBAAgB,EAAE,GAAG,GAAG,IAAI;IAI9C,MAAM,IAAI,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAI1C,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpD;;OAEG;IACH,QAAQ,IAAI,aAAa;IASzB,OAAO,CAAC,kBAAkB;CAK3B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAE5F"}
|
package/dist/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAEpG;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,IAAI,EAAE,MAAM,aAAa,GAAG,SAAS,CAAC;IAEtC;;;OAGG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,IAAI,CAAC;IAEjD;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAE3B;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,kDAAkD;IAClD,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|