@majkapp/majk-chat-mcp 1.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/README.md +854 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +287 -0
- package/dist/client/mcp-client.d.ts +22 -0
- package/dist/client/mcp-client.d.ts.map +1 -0
- package/dist/client/mcp-client.js +136 -0
- package/dist/config/parser.d.ts +14 -0
- package/dist/config/parser.d.ts.map +1 -0
- package/dist/config/parser.js +142 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/plugin.d.ts +24 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +165 -0
- package/dist/tools/dynamic-executor.d.ts +32 -0
- package/dist/tools/dynamic-executor.d.ts.map +1 -0
- package/dist/tools/dynamic-executor.js +257 -0
- package/dist/tools/mcp-tool-executor.d.ts +16 -0
- package/dist/tools/mcp-tool-executor.d.ts.map +1 -0
- package/dist/tools/mcp-tool-executor.js +93 -0
- package/dist/tools/permission-handler.d.ts +18 -0
- package/dist/tools/permission-handler.d.ts.map +1 -0
- package/dist/tools/permission-handler.js +91 -0
- package/dist/tools/tool-filter.d.ts +20 -0
- package/dist/tools/tool-filter.d.ts.map +1 -0
- package/dist/tools/tool-filter.js +93 -0
- package/dist/types/config.d.ts +56 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +2 -0
- package/package.json +54 -0
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ToolRegistry } from '@majkapp/majk-chat-core/dist/tools/tool.interface';
|
|
2
|
+
import { MCPPluginOptions } from './types/config';
|
|
3
|
+
import { ToolFilter } from './tools/tool-filter';
|
|
4
|
+
import { MCPPermissionHandler } from './tools/permission-handler';
|
|
5
|
+
export declare class MCPPlugin {
|
|
6
|
+
private options;
|
|
7
|
+
private mcpClient;
|
|
8
|
+
private toolFilter;
|
|
9
|
+
private permissionHandler;
|
|
10
|
+
private registry;
|
|
11
|
+
private registeredTools;
|
|
12
|
+
constructor(options?: MCPPluginOptions);
|
|
13
|
+
initialize(registry: ToolRegistry): Promise<void>;
|
|
14
|
+
private registerDynamicServer;
|
|
15
|
+
private connectServer;
|
|
16
|
+
private discoverAndRegisterTools;
|
|
17
|
+
shutdown(): Promise<void>;
|
|
18
|
+
getRegisteredTools(): string[];
|
|
19
|
+
getToolFilter(): ToolFilter;
|
|
20
|
+
getPermissionHandler(): MCPPermissionHandler;
|
|
21
|
+
addServer(serverName: string, serverConfig: any): Promise<void>;
|
|
22
|
+
removeServer(serverName: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgB,MAAM,mDAAmD,CAAC;AAG/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAElE,qBAAa,SAAS;IAOR,OAAO,CAAC,OAAO;IAN3B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,eAAe,CAAwC;gBAE3C,OAAO,GAAE,gBAAqB;IAa5C,UAAU,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAwBzC,qBAAqB;YA2CrB,aAAa;YAMb,wBAAwB;IAoEhC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAW/B,kBAAkB,IAAI,MAAM,EAAE;IAI9B,aAAa,IAAI,UAAU;IAI3B,oBAAoB,IAAI,oBAAoB;IAItC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/D,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAatD"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MCPPlugin = void 0;
|
|
4
|
+
const mcp_client_1 = require("./client/mcp-client");
|
|
5
|
+
const parser_1 = require("./config/parser");
|
|
6
|
+
const mcp_tool_executor_1 = require("./tools/mcp-tool-executor");
|
|
7
|
+
const dynamic_executor_1 = require("./tools/dynamic-executor");
|
|
8
|
+
const tool_filter_1 = require("./tools/tool-filter");
|
|
9
|
+
const permission_handler_1 = require("./tools/permission-handler");
|
|
10
|
+
class MCPPlugin {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
this.registry = null;
|
|
14
|
+
this.registeredTools = new Map();
|
|
15
|
+
this.mcpClient = new mcp_client_1.MCPClient(options.quiet || false);
|
|
16
|
+
this.toolFilter = new tool_filter_1.ToolFilter(options.allowedTools, options.disallowedTools);
|
|
17
|
+
this.permissionHandler = new permission_handler_1.MCPPermissionHandler(this.mcpClient, options.permissionPromptTool, options.quiet || false);
|
|
18
|
+
}
|
|
19
|
+
async initialize(registry) {
|
|
20
|
+
this.registry = registry;
|
|
21
|
+
const config = parser_1.MCPConfigParser.parseConfig(this.options);
|
|
22
|
+
for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
|
|
23
|
+
try {
|
|
24
|
+
parser_1.MCPConfigParser.validateServerConfig(serverName, serverConfig);
|
|
25
|
+
if (parser_1.MCPConfigParser.isDynamicServer(serverConfig)) {
|
|
26
|
+
// Handle dynamic server directly
|
|
27
|
+
await this.registerDynamicServer(serverName, serverConfig);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// Handle traditional MCP server
|
|
31
|
+
await this.connectServer(serverName, serverConfig);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (!this.options.quiet) {
|
|
36
|
+
console.error(`Failed to setup MCP server "${serverName}":`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async registerDynamicServer(serverName, config) {
|
|
42
|
+
for (const command of config.commands) {
|
|
43
|
+
const executor = new dynamic_executor_1.DynamicCommandExecutor(serverName, command);
|
|
44
|
+
if (this.toolFilter.isAllowed(executor.name)) {
|
|
45
|
+
if (this.options.permissionPromptTool) {
|
|
46
|
+
const originalExecute = executor.execute.bind(executor);
|
|
47
|
+
executor.execute = async (args, context) => {
|
|
48
|
+
const permissionResult = await this.permissionHandler.requestPermissionWithUpdate(executor.name, args, context);
|
|
49
|
+
if (!permissionResult.allowed) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: `Permission denied for tool: ${executor.name}`
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Use updated input if provided by permission handler
|
|
56
|
+
const finalArgs = permissionResult.updatedInput !== undefined
|
|
57
|
+
? permissionResult.updatedInput
|
|
58
|
+
: args;
|
|
59
|
+
return originalExecute(finalArgs, context);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
this.registry?.register(executor);
|
|
63
|
+
this.registeredTools.set(executor.name, executor);
|
|
64
|
+
if (!this.options.quiet) {
|
|
65
|
+
console.log(`Registered dynamic MCP tool: ${executor.name}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (!this.options.quiet) {
|
|
70
|
+
console.log(`Filtered out dynamic MCP tool: ${executor.name}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async connectServer(serverName, serverConfig) {
|
|
76
|
+
const connection = await this.mcpClient.connect(serverName, serverConfig);
|
|
77
|
+
await this.discoverAndRegisterTools(connection.serverName);
|
|
78
|
+
}
|
|
79
|
+
async discoverAndRegisterTools(serverName) {
|
|
80
|
+
const connection = this.mcpClient.getConnection(serverName);
|
|
81
|
+
if (!connection) {
|
|
82
|
+
throw new Error(`No connection found for server: ${serverName}`);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const toolsResponse = await connection.client.listTools();
|
|
86
|
+
if (!toolsResponse.tools || !Array.isArray(toolsResponse.tools)) {
|
|
87
|
+
if (!this.options.quiet) {
|
|
88
|
+
console.warn(`No tools found for MCP server "${serverName}"`);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const tool of toolsResponse.tools) {
|
|
93
|
+
const executor = new mcp_tool_executor_1.MCPToolExecutor(serverName, tool.name, tool, connection);
|
|
94
|
+
if (this.toolFilter.isAllowed(executor.name)) {
|
|
95
|
+
if (this.options.permissionPromptTool) {
|
|
96
|
+
const originalExecute = executor.execute.bind(executor);
|
|
97
|
+
executor.execute = async (args, context) => {
|
|
98
|
+
const permissionResult = await this.permissionHandler.requestPermissionWithUpdate(executor.name, args, context);
|
|
99
|
+
if (!permissionResult.allowed) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
error: `Permission denied for tool: ${executor.name}`
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Use updated input if provided by permission handler
|
|
106
|
+
const finalArgs = permissionResult.updatedInput !== undefined
|
|
107
|
+
? permissionResult.updatedInput
|
|
108
|
+
: args;
|
|
109
|
+
return originalExecute(finalArgs, context);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
this.registry?.register(executor);
|
|
113
|
+
this.registeredTools.set(executor.name, executor);
|
|
114
|
+
if (!this.options.quiet) {
|
|
115
|
+
console.log(`Registered MCP tool: ${executor.name}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
if (!this.options.quiet) {
|
|
120
|
+
console.log(`Filtered out MCP tool: ${executor.name}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (!this.options.quiet) {
|
|
127
|
+
console.error(`Failed to discover tools from server "${serverName}":`, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async shutdown() {
|
|
132
|
+
for (const toolName of this.registeredTools.keys()) {
|
|
133
|
+
if (!this.options.quiet) {
|
|
134
|
+
console.log(`Unregistering MCP tool: ${toolName}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.registeredTools.clear();
|
|
138
|
+
await this.mcpClient.disconnectAll();
|
|
139
|
+
}
|
|
140
|
+
getRegisteredTools() {
|
|
141
|
+
return Array.from(this.registeredTools.keys());
|
|
142
|
+
}
|
|
143
|
+
getToolFilter() {
|
|
144
|
+
return this.toolFilter;
|
|
145
|
+
}
|
|
146
|
+
getPermissionHandler() {
|
|
147
|
+
return this.permissionHandler;
|
|
148
|
+
}
|
|
149
|
+
async addServer(serverName, serverConfig) {
|
|
150
|
+
parser_1.MCPConfigParser.validateServerConfig(serverName, serverConfig);
|
|
151
|
+
await this.connectServer(serverName, serverConfig);
|
|
152
|
+
}
|
|
153
|
+
async removeServer(serverName) {
|
|
154
|
+
const toolsToRemove = Array.from(this.registeredTools.entries())
|
|
155
|
+
.filter(([name]) => name.startsWith(`mcp__${serverName}__`));
|
|
156
|
+
for (const [toolName] of toolsToRemove) {
|
|
157
|
+
this.registeredTools.delete(toolName);
|
|
158
|
+
if (!this.options.quiet) {
|
|
159
|
+
console.log(`Unregistered MCP tool: ${toolName}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
await this.mcpClient.disconnect(serverName);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.MCPPlugin = MCPPlugin;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ToolExecutor, ToolDefinition, ToolResult } from '@majkapp/majk-chat-core/dist/tools/tool.interface';
|
|
2
|
+
import { RequestContext } from '@majkapp/majk-chat-core/dist/types/requests';
|
|
3
|
+
import { DynamicCommand } from '../types/config';
|
|
4
|
+
export declare class DynamicCommandExecutor implements ToolExecutor {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly definition: ToolDefinition;
|
|
7
|
+
private command;
|
|
8
|
+
constructor(serverName: string, command: DynamicCommand);
|
|
9
|
+
execute(args: any, context: RequestContext): Promise<ToolResult>;
|
|
10
|
+
validateArgs(args: any): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve template variables in strings using {{variable}} syntax
|
|
13
|
+
* Supports conditionals: {{#if variable}}text{{/if}}
|
|
14
|
+
* Supports loops: {{#each array}}{{this}}{{/each}}
|
|
15
|
+
*/
|
|
16
|
+
private resolveTemplate;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve environment variables, supporting $VAR syntax
|
|
19
|
+
*/
|
|
20
|
+
private resolveEnv;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve file paths, expanding ~ to home directory
|
|
23
|
+
*/
|
|
24
|
+
private resolvePath;
|
|
25
|
+
/**
|
|
26
|
+
* Execute command and return result
|
|
27
|
+
*/
|
|
28
|
+
private executeCommand;
|
|
29
|
+
private inferSideEffects;
|
|
30
|
+
private shouldRequireConfirmation;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=dynamic-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-executor.d.ts","sourceRoot":"","sources":["../../src/tools/dynamic-executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mDAAmD,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKjD,qBAAa,sBAAuB,YAAW,YAAY;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,OAAO,CAAC,OAAO,CAAiB;gBAEpB,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc;IAiBjD,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAmEtE,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO;IAYhC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IA2CvB;;OAEG;IACH,OAAO,CAAC,UAAU;IAoBlB;;OAEG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;IACH,OAAO,CAAC,cAAc;IA4CtB,OAAO,CAAC,gBAAgB;IA6BxB,OAAO,CAAC,yBAAyB;CAIlC"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DynamicCommandExecutor = void 0;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
class DynamicCommandExecutor {
|
|
41
|
+
constructor(serverName, command) {
|
|
42
|
+
this.name = `mcp__${serverName}__${command.name}`;
|
|
43
|
+
this.command = command;
|
|
44
|
+
this.definition = {
|
|
45
|
+
type: 'function',
|
|
46
|
+
function: {
|
|
47
|
+
name: this.name,
|
|
48
|
+
description: command.description,
|
|
49
|
+
parameters: command.parameters
|
|
50
|
+
},
|
|
51
|
+
serverInvocable: true,
|
|
52
|
+
sideEffects: this.inferSideEffects(command),
|
|
53
|
+
requiresConfirmation: this.shouldRequireConfirmation(command)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async execute(args, context) {
|
|
57
|
+
try {
|
|
58
|
+
// Resolve template variables in command path and args
|
|
59
|
+
const resolvedPath = this.resolveTemplate(this.command.exec.path, args);
|
|
60
|
+
const resolvedArgs = this.command.exec.args.map(arg => this.resolveTemplate(arg, args)).filter(arg => arg.length > 0); // Remove empty args from conditionals
|
|
61
|
+
// Prepare environment
|
|
62
|
+
const env = {
|
|
63
|
+
...process.env,
|
|
64
|
+
...this.resolveEnv(this.command.exec.env || {}, args)
|
|
65
|
+
};
|
|
66
|
+
// Resolve working directory
|
|
67
|
+
const cwd = this.command.exec.cwd
|
|
68
|
+
? this.resolvePath(this.command.exec.cwd)
|
|
69
|
+
: process.cwd();
|
|
70
|
+
const timeout = this.command.exec.timeoutMs || 30000;
|
|
71
|
+
const expectFormat = this.command.exec.expect || 'text';
|
|
72
|
+
// Execute command
|
|
73
|
+
const result = await this.executeCommand(resolvedPath, resolvedArgs, { env, cwd, timeout });
|
|
74
|
+
// Parse result based on expected format
|
|
75
|
+
let output;
|
|
76
|
+
if (expectFormat === 'json') {
|
|
77
|
+
try {
|
|
78
|
+
output = JSON.parse(result.stdout);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: `Failed to parse JSON output: ${e}. Raw output: ${result.stdout}`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
output = result.stdout;
|
|
89
|
+
}
|
|
90
|
+
// Check for errors
|
|
91
|
+
if (result.code !== 0) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: `Command failed with exit code ${result.code}: ${result.stderr}`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
output,
|
|
100
|
+
metadata: {
|
|
101
|
+
command: resolvedPath,
|
|
102
|
+
args: resolvedArgs,
|
|
103
|
+
exitCode: result.code,
|
|
104
|
+
stderr: result.stderr
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
error: error instanceof Error ? error.message : String(error)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
validateArgs(args) {
|
|
116
|
+
const schema = this.command.parameters;
|
|
117
|
+
if (schema.required) {
|
|
118
|
+
for (const requiredField of schema.required) {
|
|
119
|
+
if (!(requiredField in args)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve template variables in strings using {{variable}} syntax
|
|
128
|
+
* Supports conditionals: {{#if variable}}text{{/if}}
|
|
129
|
+
* Supports loops: {{#each array}}{{this}}{{/each}}
|
|
130
|
+
*/
|
|
131
|
+
resolveTemplate(template, args) {
|
|
132
|
+
let result = template;
|
|
133
|
+
let prevResult = '';
|
|
134
|
+
// Process templates recursively until no more changes occur (handles nested templates)
|
|
135
|
+
while (result !== prevResult) {
|
|
136
|
+
prevResult = result;
|
|
137
|
+
// Handle conditionals: {{#if variable}}text{{/if}}
|
|
138
|
+
result = result.replace(/\{\{#if\s+(\w+)\}\}(.*?)\{\{\/if\}\}/g, (match, variable, content) => {
|
|
139
|
+
return args[variable] ? content : '';
|
|
140
|
+
});
|
|
141
|
+
// Handle each loops: {{#each array}}{{this}}{{/each}}
|
|
142
|
+
result = result.replace(/\{\{#each\s+(\w+)\}\}(.*?)\{\{\/each\}\}/g, (match, variable, content) => {
|
|
143
|
+
const array = args[variable];
|
|
144
|
+
if (Array.isArray(array)) {
|
|
145
|
+
return array.map(item => content.replace(/\{\{this\}\}/g, String(item))).join(' ');
|
|
146
|
+
}
|
|
147
|
+
return '';
|
|
148
|
+
});
|
|
149
|
+
// Handle simple variables: {{variable}} (including nested properties like config.debug)
|
|
150
|
+
result = result.replace(/\{\{([\w.]+)\}\}/g, (match, variable) => {
|
|
151
|
+
// Handle nested properties
|
|
152
|
+
if (variable.includes('.')) {
|
|
153
|
+
return ''; // Don't resolve nested properties as per test expectation
|
|
154
|
+
}
|
|
155
|
+
return args[variable] !== undefined ? String(args[variable]) : '';
|
|
156
|
+
});
|
|
157
|
+
// Handle environment variables: $VAR
|
|
158
|
+
result = result.replace(/\$(\w+)/g, (match, variable) => {
|
|
159
|
+
return process.env[variable] || '';
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// Clean up multiple consecutive spaces but preserve other characters
|
|
163
|
+
result = result.replace(/ {2,}/g, ' ');
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve environment variables, supporting $VAR syntax
|
|
168
|
+
*/
|
|
169
|
+
resolveEnv(envConfig, args) {
|
|
170
|
+
const resolved = {};
|
|
171
|
+
for (const [key, value] of Object.entries(envConfig)) {
|
|
172
|
+
let resolvedValue = value;
|
|
173
|
+
// Replace $VAR with process.env.VAR
|
|
174
|
+
resolvedValue = resolvedValue.replace(/\$(\w+)/g, (match, varName) => {
|
|
175
|
+
return process.env[varName] || '';
|
|
176
|
+
});
|
|
177
|
+
// Replace template variables
|
|
178
|
+
resolvedValue = this.resolveTemplate(resolvedValue, args);
|
|
179
|
+
resolved[key] = resolvedValue;
|
|
180
|
+
}
|
|
181
|
+
return resolved;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Resolve file paths, expanding ~ to home directory
|
|
185
|
+
*/
|
|
186
|
+
resolvePath(inputPath) {
|
|
187
|
+
if (inputPath.startsWith('~')) {
|
|
188
|
+
return path.join(os.homedir(), inputPath.slice(1));
|
|
189
|
+
}
|
|
190
|
+
return path.resolve(inputPath);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Execute command and return result
|
|
194
|
+
*/
|
|
195
|
+
executeCommand(command, args, options) {
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
198
|
+
env: options.env,
|
|
199
|
+
cwd: options.cwd,
|
|
200
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
201
|
+
});
|
|
202
|
+
let stdout = '';
|
|
203
|
+
let stderr = '';
|
|
204
|
+
child.stdout?.on('data', (data) => {
|
|
205
|
+
stdout += data.toString();
|
|
206
|
+
});
|
|
207
|
+
child.stderr?.on('data', (data) => {
|
|
208
|
+
stderr += data.toString();
|
|
209
|
+
});
|
|
210
|
+
const timeout = setTimeout(() => {
|
|
211
|
+
child.kill('SIGKILL');
|
|
212
|
+
reject(new Error(`Command timed out after ${options.timeout}ms`));
|
|
213
|
+
}, options.timeout);
|
|
214
|
+
child.on('close', (code) => {
|
|
215
|
+
clearTimeout(timeout);
|
|
216
|
+
resolve({
|
|
217
|
+
code: code || 0,
|
|
218
|
+
stdout: stdout.trim(),
|
|
219
|
+
stderr: stderr.trim()
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
child.on('error', (error) => {
|
|
223
|
+
clearTimeout(timeout);
|
|
224
|
+
reject(error);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
inferSideEffects(command) {
|
|
229
|
+
const name = command.name.toLowerCase();
|
|
230
|
+
const description = command.description.toLowerCase();
|
|
231
|
+
const path = command.exec.path.toLowerCase();
|
|
232
|
+
// Check for external operations first (network calls, APIs)
|
|
233
|
+
if (path.includes('curl') || path.includes('wget') || path.includes('http') ||
|
|
234
|
+
name.includes('api') || description.includes('api') || description.includes('http') ||
|
|
235
|
+
description.includes('network') || description.includes('download')) {
|
|
236
|
+
return 'external';
|
|
237
|
+
}
|
|
238
|
+
if (name.includes('write') || name.includes('create') ||
|
|
239
|
+
name.includes('update') || name.includes('delete') ||
|
|
240
|
+
description.includes('write') || description.includes('create') ||
|
|
241
|
+
description.includes('update') || description.includes('delete')) {
|
|
242
|
+
return 'write';
|
|
243
|
+
}
|
|
244
|
+
if (name.includes('read') || name.includes('get') ||
|
|
245
|
+
name.includes('list') || name.includes('fetch') ||
|
|
246
|
+
description.includes('read') || description.includes('get') ||
|
|
247
|
+
description.includes('list') || description.includes('fetch')) {
|
|
248
|
+
return 'read';
|
|
249
|
+
}
|
|
250
|
+
return 'external'; // Dynamic commands are typically external
|
|
251
|
+
}
|
|
252
|
+
shouldRequireConfirmation(command) {
|
|
253
|
+
const sideEffects = this.inferSideEffects(command);
|
|
254
|
+
return sideEffects === 'write' || sideEffects === 'external';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.DynamicCommandExecutor = DynamicCommandExecutor;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ToolExecutor, ToolDefinition, ToolResult } from '@majkapp/majk-chat-core/dist/tools/tool.interface';
|
|
2
|
+
import { RequestContext } from '@majkapp/majk-chat-core/dist/types/requests';
|
|
3
|
+
import { MCPConnection } from '../client/mcp-client';
|
|
4
|
+
export declare class MCPToolExecutor implements ToolExecutor {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly definition: ToolDefinition;
|
|
7
|
+
private connection;
|
|
8
|
+
private originalToolName;
|
|
9
|
+
private inputSchema;
|
|
10
|
+
constructor(serverName: string, toolName: string, definition: any, connection: MCPConnection);
|
|
11
|
+
execute(args: any, context: RequestContext): Promise<ToolResult>;
|
|
12
|
+
validateArgs(args: any): boolean;
|
|
13
|
+
private inferSideEffects;
|
|
14
|
+
private shouldRequireConfirmation;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=mcp-tool-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-tool-executor.d.ts","sourceRoot":"","sources":["../../src/tools/mcp-tool-executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mDAAmD,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,qBAAa,eAAgB,YAAW,YAAY;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAM;gBAGvB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,GAAG,EACf,UAAU,EAAE,aAAa;IAoBrB,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IA8BtE,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO;IAiBhC,OAAO,CAAC,gBAAgB;IA4BxB,OAAO,CAAC,yBAAyB;CAIlC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MCPToolExecutor = void 0;
|
|
4
|
+
const parser_1 = require("../config/parser");
|
|
5
|
+
class MCPToolExecutor {
|
|
6
|
+
constructor(serverName, toolName, definition, connection) {
|
|
7
|
+
this.originalToolName = toolName;
|
|
8
|
+
this.name = parser_1.MCPConfigParser.formatToolName(serverName, toolName);
|
|
9
|
+
this.connection = connection;
|
|
10
|
+
this.inputSchema = definition.inputSchema || definition.parameters || {};
|
|
11
|
+
this.definition = {
|
|
12
|
+
type: 'function',
|
|
13
|
+
function: {
|
|
14
|
+
name: this.name,
|
|
15
|
+
description: definition.description || '',
|
|
16
|
+
parameters: this.inputSchema
|
|
17
|
+
},
|
|
18
|
+
serverInvocable: true,
|
|
19
|
+
sideEffects: this.inferSideEffects(definition),
|
|
20
|
+
requiresConfirmation: this.shouldRequireConfirmation(definition)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async execute(args, context) {
|
|
24
|
+
try {
|
|
25
|
+
const result = await this.connection.client.callTool({
|
|
26
|
+
name: this.originalToolName,
|
|
27
|
+
arguments: args
|
|
28
|
+
});
|
|
29
|
+
if (result.isError) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: result.text || 'Tool execution failed'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
output: result.content,
|
|
38
|
+
metadata: {
|
|
39
|
+
serverName: this.connection.serverName,
|
|
40
|
+
originalToolName: this.originalToolName
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: error instanceof Error ? error.message : String(error)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
validateArgs(args) {
|
|
52
|
+
const schema = this.inputSchema;
|
|
53
|
+
if (!schema || typeof schema !== 'object') {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (schema.type === 'object' && schema.required) {
|
|
57
|
+
for (const requiredField of schema.required) {
|
|
58
|
+
if (!(requiredField in args)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
inferSideEffects(definition) {
|
|
66
|
+
const name = definition.name?.toLowerCase() || '';
|
|
67
|
+
const description = definition.description?.toLowerCase() || '';
|
|
68
|
+
if (name.includes('write') || name.includes('create') ||
|
|
69
|
+
name.includes('update') || name.includes('delete') ||
|
|
70
|
+
description.includes('write') || description.includes('create') ||
|
|
71
|
+
description.includes('update') || description.includes('delete')) {
|
|
72
|
+
return 'write';
|
|
73
|
+
}
|
|
74
|
+
if (name.includes('read') || name.includes('get') ||
|
|
75
|
+
name.includes('list') || name.includes('fetch') ||
|
|
76
|
+
description.includes('read') || description.includes('get') ||
|
|
77
|
+
description.includes('list') || description.includes('fetch')) {
|
|
78
|
+
return 'read';
|
|
79
|
+
}
|
|
80
|
+
if (name.includes('http') || name.includes('api') ||
|
|
81
|
+
name.includes('request') || name.includes('call') ||
|
|
82
|
+
description.includes('http') || description.includes('api') ||
|
|
83
|
+
description.includes('request') || description.includes('external')) {
|
|
84
|
+
return 'external';
|
|
85
|
+
}
|
|
86
|
+
return 'none';
|
|
87
|
+
}
|
|
88
|
+
shouldRequireConfirmation(definition) {
|
|
89
|
+
const sideEffects = this.inferSideEffects(definition);
|
|
90
|
+
return sideEffects === 'write' || sideEffects === 'external';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.MCPToolExecutor = MCPToolExecutor;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PermissionHandler } from '@majkapp/majk-chat-core/dist/tools/tool.interface';
|
|
2
|
+
import { RequestContext } from '@majkapp/majk-chat-core/dist/types/requests';
|
|
3
|
+
import { MCPClient } from '../client/mcp-client';
|
|
4
|
+
export interface PermissionResult {
|
|
5
|
+
allowed: boolean;
|
|
6
|
+
updatedInput?: any;
|
|
7
|
+
}
|
|
8
|
+
export declare class MCPPermissionHandler implements PermissionHandler {
|
|
9
|
+
private quiet;
|
|
10
|
+
private mcpClient;
|
|
11
|
+
private permissionTool;
|
|
12
|
+
constructor(mcpClient: MCPClient, permissionToolName?: string, quiet?: boolean);
|
|
13
|
+
requestPermission(toolName: string, args: any, context: RequestContext): Promise<boolean>;
|
|
14
|
+
requestPermissionWithUpdate(toolName: string, args: any, context: RequestContext): Promise<PermissionResult>;
|
|
15
|
+
setPermissionTool(toolName: string): void;
|
|
16
|
+
getPermissionTool(): string | null;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=permission-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-handler.d.ts","sourceRoot":"","sources":["../../src/tools/permission-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mDAAmD,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAiB,MAAM,sBAAsB,CAAC;AAIhE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,qBAAa,oBAAqB,YAAW,iBAAiB;IAIG,OAAO,CAAC,KAAK;IAH5E,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,cAAc,CAAqC;gBAE/C,SAAS,EAAE,SAAS,EAAE,kBAAkB,CAAC,EAAE,MAAM,EAAU,KAAK,GAAE,OAAe;IAWvF,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,GAAG,EACT,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC;IAKb,2BAA2B,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,GAAG,EACT,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,gBAAgB,CAAC;IAmE5B,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOzC,iBAAiB,IAAI,MAAM,GAAG,IAAI;CAGnC"}
|