@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
package/README.md
CHANGED
|
@@ -62,6 +62,62 @@ notion-mcp-cli notion-retrieve-page --page_id "abc123"
|
|
|
62
62
|
notion-mcp-cli notion-list-users
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## Turn a goke CLI into an MCP server
|
|
66
|
+
|
|
67
|
+
`addCliToolsToMcp()` does the inverse mapping: every CLI command becomes an MCP tool.
|
|
68
|
+
|
|
69
|
+
- Command description → MCP tool description
|
|
70
|
+
- Option schema (Zod or any Standard Schema library) → MCP `inputSchema` JSON Schema
|
|
71
|
+
- Command names are sanitized into valid MCP tool names (invalid characters become `_`)
|
|
72
|
+
- Composable with existing MCP tools already registered on the same server
|
|
73
|
+
|
|
74
|
+
### With low-level `Server`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { goke } from "goke"
|
|
78
|
+
import { z } from "zod"
|
|
79
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
|
|
80
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
81
|
+
import { addCliToolsToMcp } from "@goke/mcp"
|
|
82
|
+
|
|
83
|
+
const cli = goke("my-cli")
|
|
84
|
+
|
|
85
|
+
cli
|
|
86
|
+
.command("notion search", "Search Notion pages")
|
|
87
|
+
.option("--query <query>", z.string().describe("Search query"))
|
|
88
|
+
.action((options) => ({ query: options.query }))
|
|
89
|
+
|
|
90
|
+
const server = new Server(
|
|
91
|
+
{ name: "my-cli-mcp", version: "1.0.0" },
|
|
92
|
+
{ capabilities: {} },
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
addCliToolsToMcp({ cli, server })
|
|
96
|
+
|
|
97
|
+
const transport = new StdioServerTransport()
|
|
98
|
+
await server.connect(transport)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Run it with Node:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
node dist/server.js
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### With high-level `McpServer`
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
|
111
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
112
|
+
import { addCliToolsToMcp } from "@goke/mcp"
|
|
113
|
+
|
|
114
|
+
const mcp = new McpServer({ name: "my-cli-mcp", version: "1.0.0" })
|
|
115
|
+
addCliToolsToMcp({ cli, server: mcp })
|
|
116
|
+
|
|
117
|
+
const transport = new StdioServerTransport()
|
|
118
|
+
await mcp.connect(transport)
|
|
119
|
+
```
|
|
120
|
+
|
|
65
121
|
## Full example (with config persistence)
|
|
66
122
|
|
|
67
123
|
This is the pattern used by [notion-mcp-cli](../notion-mcp-cli):
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import { type Goke } from "goke";
|
|
10
|
+
export interface AddCliToolsToMcpOptions {
|
|
11
|
+
cli: Goke;
|
|
12
|
+
server: Server | McpServer;
|
|
13
|
+
commandFilter?: (commandName: string) => boolean;
|
|
14
|
+
sanitizeToolName?: (commandName: string) => string;
|
|
15
|
+
}
|
|
16
|
+
export declare function addCliToolsToMcp(options: AddCliToolsToMcpOptions): void;
|
|
17
|
+
//# sourceMappingURL=cli-to-mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-to-mcp.d.ts","sourceRoot":"","sources":["../../src/cli-to-mcp.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AASxE,OAAO,EAAmD,KAAK,IAAI,EAA6B,MAAM,MAAM,CAAC;AAwD7G,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,IAAI,CAAC;IACV,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,gBAAgB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;CACpD;AA4XD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAyCvE"}
|
|
@@ -0,0 +1,380 @@
|
|
|
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
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { coerceBySchema, extractJsonSchema } from "goke";
|
|
9
|
+
const CLI_TO_MCP_STATE = Symbol.for("@goke/mcp/cli-to-mcp-state");
|
|
10
|
+
function isMountableCommand(command, commandFilter) {
|
|
11
|
+
if (!command.commandAction) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (command.name === "") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (commandFilter && !commandFilter(command.name)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
function isMcpServer(value) {
|
|
23
|
+
return "server" in value;
|
|
24
|
+
}
|
|
25
|
+
function resolveServer(value) {
|
|
26
|
+
if (isMcpServer(value)) {
|
|
27
|
+
return value.server;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function getToolCallArguments(args, name) {
|
|
32
|
+
if (name in args) {
|
|
33
|
+
return args[name];
|
|
34
|
+
}
|
|
35
|
+
const parts = name.split(".");
|
|
36
|
+
let current = args;
|
|
37
|
+
for (const part of parts) {
|
|
38
|
+
if (current != null && typeof current === "object" && part in current) {
|
|
39
|
+
current = current[part];
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
function setDotProp(target, keys, value) {
|
|
48
|
+
let current = target;
|
|
49
|
+
for (let i = 0; i < keys.length; i++) {
|
|
50
|
+
const key = keys[i];
|
|
51
|
+
if (i === keys.length - 1) {
|
|
52
|
+
current[key] = value;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const existing = current[key];
|
|
56
|
+
if (existing != null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
57
|
+
current = existing;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const next = {};
|
|
61
|
+
current[key] = next;
|
|
62
|
+
current = next;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function defaultSanitizeToolName(commandName) {
|
|
66
|
+
let name = commandName.trim();
|
|
67
|
+
name = name.replace(/\s+/g, "_");
|
|
68
|
+
name = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
69
|
+
name = name.replace(/_+/g, "_");
|
|
70
|
+
name = name.replace(/^[._-]+|[._-]+$/g, "");
|
|
71
|
+
if (!name) {
|
|
72
|
+
name = "tool";
|
|
73
|
+
}
|
|
74
|
+
if (name.length > 128) {
|
|
75
|
+
name = name.slice(0, 128);
|
|
76
|
+
}
|
|
77
|
+
return name;
|
|
78
|
+
}
|
|
79
|
+
function uniqueToolName(baseName, usedNames) {
|
|
80
|
+
if (!usedNames.has(baseName)) {
|
|
81
|
+
return baseName;
|
|
82
|
+
}
|
|
83
|
+
for (let i = 2; i < 10_000; i++) {
|
|
84
|
+
const suffix = `_${i}`;
|
|
85
|
+
const prefixMax = 128 - suffix.length;
|
|
86
|
+
const candidate = `${baseName.slice(0, Math.max(1, prefixMax))}${suffix}`;
|
|
87
|
+
if (!usedNames.has(candidate)) {
|
|
88
|
+
return candidate;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`Unable to generate a unique MCP tool name for ${baseName}`);
|
|
92
|
+
}
|
|
93
|
+
function normalizeOptionSchema(option) {
|
|
94
|
+
const schemaFromOption = option.schema ? extractJsonSchema(option.schema) : undefined;
|
|
95
|
+
const schema = schemaFromOption ? { ...schemaFromOption } : {
|
|
96
|
+
type: option.isBoolean ? "boolean" : "string",
|
|
97
|
+
};
|
|
98
|
+
if (typeof schema.description !== "string" && option.description) {
|
|
99
|
+
schema.description = option.description;
|
|
100
|
+
}
|
|
101
|
+
if (schema.default === undefined && option.default !== undefined) {
|
|
102
|
+
schema.default = option.default;
|
|
103
|
+
}
|
|
104
|
+
return { schema, jsonSchema: schemaFromOption };
|
|
105
|
+
}
|
|
106
|
+
function commandDescription(command) {
|
|
107
|
+
const description = command.description.trim();
|
|
108
|
+
if (description) {
|
|
109
|
+
return description;
|
|
110
|
+
}
|
|
111
|
+
return `Run CLI command ${command.name}`;
|
|
112
|
+
}
|
|
113
|
+
function formatTextResult(value) {
|
|
114
|
+
if (typeof value === "string") {
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
if (value == null) {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
121
|
+
return String(value);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
return JSON.stringify(value, null, 2);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return String(value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function toCallToolResult(value) {
|
|
131
|
+
if (value && typeof value === "object" && "content" in value) {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
content: [{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: formatTextResult(value),
|
|
138
|
+
}],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function getExistingRequestHandler(server, method) {
|
|
142
|
+
const handlerMap = server._requestHandlers;
|
|
143
|
+
if (!(handlerMap instanceof Map)) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
return handlerMap.get(method);
|
|
147
|
+
}
|
|
148
|
+
function isToolNotFoundError(error, toolName) {
|
|
149
|
+
if (!(error instanceof McpError)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (error.code !== ErrorCode.InvalidParams) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const message = String(error.message).toLowerCase();
|
|
156
|
+
return message.includes("tool") && message.includes("not found") && message.includes(toolName.toLowerCase());
|
|
157
|
+
}
|
|
158
|
+
function isToolNotFoundResult(result, toolName) {
|
|
159
|
+
if (!result || typeof result !== "object") {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
const maybe = result;
|
|
163
|
+
if (!maybe.isError || !Array.isArray(maybe.content)) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const textBlock = maybe.content.find((entry) => entry?.type === "text");
|
|
167
|
+
const text = String(textBlock?.text ?? "").toLowerCase();
|
|
168
|
+
return text.includes("tool") && text.includes("not found") && text.includes(toolName.toLowerCase());
|
|
169
|
+
}
|
|
170
|
+
async function runCliTool(binding, argumentsObject) {
|
|
171
|
+
for (const requiredName of binding.requiredNames) {
|
|
172
|
+
if (getToolCallArguments(argumentsObject, requiredName) === undefined) {
|
|
173
|
+
throw new McpError(ErrorCode.InvalidParams, `Missing required argument: ${requiredName}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const positionalValues = [];
|
|
177
|
+
for (const arg of binding.positionalArgs) {
|
|
178
|
+
const value = getToolCallArguments(argumentsObject, arg.value);
|
|
179
|
+
if (arg.variadic) {
|
|
180
|
+
if (value === undefined) {
|
|
181
|
+
positionalValues.push([]);
|
|
182
|
+
}
|
|
183
|
+
else if (Array.isArray(value)) {
|
|
184
|
+
positionalValues.push(value.map((entry) => String(entry)));
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
positionalValues.push([String(value)]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
positionalValues.push(value === undefined ? undefined : String(value));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const optionsObject = {};
|
|
195
|
+
for (const option of binding.options) {
|
|
196
|
+
let optionValue = getToolCallArguments(argumentsObject, option.name);
|
|
197
|
+
if (optionValue === undefined && option.defaultValue !== undefined) {
|
|
198
|
+
optionValue = option.defaultValue;
|
|
199
|
+
}
|
|
200
|
+
if (optionValue !== undefined && option.jsonSchema) {
|
|
201
|
+
const isStringArray = Array.isArray(optionValue) && optionValue.every((value) => typeof value === "string");
|
|
202
|
+
const isCoercibleType = typeof optionValue === "string" || typeof optionValue === "boolean" || isStringArray;
|
|
203
|
+
if (isCoercibleType) {
|
|
204
|
+
optionValue = coerceBySchema(optionValue, option.jsonSchema, option.name);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (optionValue !== undefined) {
|
|
208
|
+
setDotProp(optionsObject, option.name.split("."), optionValue);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const action = binding.command.commandAction;
|
|
212
|
+
if (!action) {
|
|
213
|
+
throw new McpError(ErrorCode.InvalidParams, `Command ${binding.command.name} has no action`);
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const result = await Promise.resolve(action(...positionalValues, optionsObject));
|
|
217
|
+
return toCallToolResult(result);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
221
|
+
return {
|
|
222
|
+
isError: true,
|
|
223
|
+
content: [{ type: "text", text: message }],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function createBinding(command, toolName) {
|
|
228
|
+
const positionalArgs = command.args;
|
|
229
|
+
const options = command.options;
|
|
230
|
+
const properties = {};
|
|
231
|
+
const requiredNames = [];
|
|
232
|
+
const optionBindings = [];
|
|
233
|
+
for (const arg of positionalArgs) {
|
|
234
|
+
if (arg.variadic) {
|
|
235
|
+
properties[arg.value] = {
|
|
236
|
+
type: "array",
|
|
237
|
+
items: { type: "string" },
|
|
238
|
+
description: `Positional argument ${arg.value}`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
properties[arg.value] = {
|
|
243
|
+
type: "string",
|
|
244
|
+
description: `Positional argument ${arg.value}`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (arg.required) {
|
|
248
|
+
requiredNames.push(arg.value);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
for (const option of options) {
|
|
252
|
+
const normalized = normalizeOptionSchema(option);
|
|
253
|
+
properties[option.name] = normalized.schema;
|
|
254
|
+
if (option.required) {
|
|
255
|
+
requiredNames.push(option.name);
|
|
256
|
+
}
|
|
257
|
+
optionBindings.push({
|
|
258
|
+
name: option.name,
|
|
259
|
+
defaultValue: option.default,
|
|
260
|
+
jsonSchema: normalized.jsonSchema,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const inputSchema = {
|
|
264
|
+
type: "object",
|
|
265
|
+
properties,
|
|
266
|
+
...(requiredNames.length > 0 ? { required: Array.from(new Set(requiredNames)) } : {}),
|
|
267
|
+
};
|
|
268
|
+
return {
|
|
269
|
+
tool: {
|
|
270
|
+
name: toolName,
|
|
271
|
+
description: commandDescription(command),
|
|
272
|
+
inputSchema,
|
|
273
|
+
},
|
|
274
|
+
command,
|
|
275
|
+
positionalArgs,
|
|
276
|
+
options: optionBindings,
|
|
277
|
+
requiredNames: Array.from(new Set(requiredNames)),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function getOrInstallState(server) {
|
|
281
|
+
const serverWithState = server;
|
|
282
|
+
const existing = serverWithState[CLI_TO_MCP_STATE];
|
|
283
|
+
if (existing) {
|
|
284
|
+
return existing;
|
|
285
|
+
}
|
|
286
|
+
const existingListHandler = getExistingRequestHandler(server, "tools/list");
|
|
287
|
+
const existingCallHandler = getExistingRequestHandler(server, "tools/call");
|
|
288
|
+
if (!existingListHandler && !existingCallHandler) {
|
|
289
|
+
server.registerCapabilities({ tools: { listChanged: true } });
|
|
290
|
+
}
|
|
291
|
+
const state = {
|
|
292
|
+
toolsByName: new Map(),
|
|
293
|
+
commandToToolName: new Map(),
|
|
294
|
+
};
|
|
295
|
+
server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
296
|
+
const localTools = Array.from(state.toolsByName.values()).map((binding) => binding.tool);
|
|
297
|
+
if (!existingListHandler) {
|
|
298
|
+
return { tools: localTools };
|
|
299
|
+
}
|
|
300
|
+
const previousResult = await Promise.resolve(existingListHandler(request, extra));
|
|
301
|
+
const merged = new Map();
|
|
302
|
+
for (const tool of previousResult.tools ?? []) {
|
|
303
|
+
merged.set(tool.name, tool);
|
|
304
|
+
}
|
|
305
|
+
for (const tool of localTools) {
|
|
306
|
+
if (!merged.has(tool.name)) {
|
|
307
|
+
merged.set(tool.name, tool);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
...previousResult,
|
|
312
|
+
tools: Array.from(merged.values()),
|
|
313
|
+
};
|
|
314
|
+
});
|
|
315
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
316
|
+
const binding = state.toolsByName.get(request.params.name);
|
|
317
|
+
const argumentsObject = request.params.arguments ?? {};
|
|
318
|
+
if (existingCallHandler) {
|
|
319
|
+
try {
|
|
320
|
+
const existingResult = await Promise.resolve(existingCallHandler(request, extra));
|
|
321
|
+
if (binding && isToolNotFoundResult(existingResult, request.params.name)) {
|
|
322
|
+
return runCliTool(binding, argumentsObject);
|
|
323
|
+
}
|
|
324
|
+
return existingResult;
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
if (!binding || !isToolNotFoundError(error, request.params.name)) {
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (!binding) {
|
|
333
|
+
throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
|
|
334
|
+
}
|
|
335
|
+
return runCliTool(binding, argumentsObject);
|
|
336
|
+
});
|
|
337
|
+
Object.defineProperty(serverWithState, CLI_TO_MCP_STATE, {
|
|
338
|
+
value: state,
|
|
339
|
+
enumerable: false,
|
|
340
|
+
writable: false,
|
|
341
|
+
configurable: false,
|
|
342
|
+
});
|
|
343
|
+
return state;
|
|
344
|
+
}
|
|
345
|
+
export function addCliToolsToMcp(options) {
|
|
346
|
+
const { cli, commandFilter, sanitizeToolName = defaultSanitizeToolName } = options;
|
|
347
|
+
const server = resolveServer(options.server);
|
|
348
|
+
const state = getOrInstallState(server);
|
|
349
|
+
const usedNames = new Set(state.toolsByName.keys());
|
|
350
|
+
const activeCommandNames = new Set();
|
|
351
|
+
for (const command of cli.commands) {
|
|
352
|
+
if (isMountableCommand(command, commandFilter)) {
|
|
353
|
+
activeCommandNames.add(command.name);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
for (const [commandName, toolName] of state.commandToToolName) {
|
|
357
|
+
if (!activeCommandNames.has(commandName)) {
|
|
358
|
+
state.commandToToolName.delete(commandName);
|
|
359
|
+
state.toolsByName.delete(toolName);
|
|
360
|
+
usedNames.delete(toolName);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const command of cli.commands) {
|
|
364
|
+
if (!isMountableCommand(command, commandFilter)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const existingToolName = state.commandToToolName.get(command.name);
|
|
368
|
+
if (existingToolName) {
|
|
369
|
+
state.toolsByName.delete(existingToolName);
|
|
370
|
+
state.commandToToolName.delete(command.name);
|
|
371
|
+
usedNames.delete(existingToolName);
|
|
372
|
+
}
|
|
373
|
+
const baseToolName = defaultSanitizeToolName(sanitizeToolName(command.name));
|
|
374
|
+
const toolName = uniqueToolName(baseToolName, usedNames);
|
|
375
|
+
usedNames.add(toolName);
|
|
376
|
+
const binding = createBinding(command, toolName);
|
|
377
|
+
state.toolsByName.set(toolName, binding);
|
|
378
|
+
state.commandToToolName.set(command.name, toolName);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
43
43
|
import type { Goke } from "goke";
|
|
44
44
|
import type { McpOAuthConfig } from "./types.js";
|
|
45
|
+
export { addCliToolsToMcp } from "./cli-to-mcp.js";
|
|
46
|
+
export type { AddCliToolsToMcpOptions } from "./cli-to-mcp.js";
|
|
45
47
|
export type { Transport };
|
|
46
48
|
export type { McpOAuthConfig, McpOAuthState } from "./types.js";
|
|
47
49
|
export interface CachedMcpTools {
|
|
@@ -0,0 +1 @@
|
|
|
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;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG/D,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"}
|
|
@@ -45,6 +45,7 @@ import { wrapJsonSchema } from "goke";
|
|
|
45
45
|
import yaml from "js-yaml";
|
|
46
46
|
import { FileOAuthProvider } from "./oauth-provider.js";
|
|
47
47
|
import { startOAuthFlow, isAuthRequiredError } from "./auth.js";
|
|
48
|
+
export { addCliToolsToMcp } from "./cli-to-mcp.js";
|
|
48
49
|
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
49
50
|
/**
|
|
50
51
|
* Check if a schema represents a complex type (object/array) for help text display.
|
|
@@ -274,7 +275,7 @@ export async function addMcpCommands(options) {
|
|
|
274
275
|
}
|
|
275
276
|
else {
|
|
276
277
|
// Wrap the MCP tool's JSON Schema property into a StandardJSONSchemaV1
|
|
277
|
-
//
|
|
278
|
+
// so goke can use it for type coercion.
|
|
278
279
|
// Put the enriched description into the JSON Schema so it's extracted automatically.
|
|
279
280
|
// Boolean flags with defaults also go through this path to preserve the default.
|
|
280
281
|
const enrichedSchema = { ...propSchema, description: optionDesc };
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-cli-tools-to-mcp.test.d.ts","sourceRoot":"","sources":["../../test/add-cli-tools-to-mcp.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|