mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2026-03-23 09:39:29 -04:00
MCP checkbox for always allow
This commit is contained in:
17
src/__mocks__/@modelcontextprotocol/sdk/client/index.js
Normal file
17
src/__mocks__/@modelcontextprotocol/sdk/client/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
class Client {
|
||||
constructor() {
|
||||
this.request = jest.fn()
|
||||
}
|
||||
|
||||
connect() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
close() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Client
|
||||
}
|
||||
22
src/__mocks__/@modelcontextprotocol/sdk/client/stdio.js
Normal file
22
src/__mocks__/@modelcontextprotocol/sdk/client/stdio.js
Normal file
@@ -0,0 +1,22 @@
|
||||
class StdioClientTransport {
|
||||
constructor() {
|
||||
this.start = jest.fn().mockResolvedValue(undefined)
|
||||
this.close = jest.fn().mockResolvedValue(undefined)
|
||||
this.stderr = {
|
||||
on: jest.fn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StdioServerParameters {
|
||||
constructor() {
|
||||
this.command = ''
|
||||
this.args = []
|
||||
this.env = {}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
StdioClientTransport,
|
||||
StdioServerParameters
|
||||
}
|
||||
24
src/__mocks__/@modelcontextprotocol/sdk/index.js
Normal file
24
src/__mocks__/@modelcontextprotocol/sdk/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const { Client } = require('./client/index.js')
|
||||
const { StdioClientTransport, StdioServerParameters } = require('./client/stdio.js')
|
||||
const {
|
||||
CallToolResultSchema,
|
||||
ListToolsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ReadResourceResultSchema,
|
||||
ErrorCode,
|
||||
McpError
|
||||
} = require('./types.js')
|
||||
|
||||
module.exports = {
|
||||
Client,
|
||||
StdioClientTransport,
|
||||
StdioServerParameters,
|
||||
CallToolResultSchema,
|
||||
ListToolsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ReadResourceResultSchema,
|
||||
ErrorCode,
|
||||
McpError
|
||||
}
|
||||
51
src/__mocks__/@modelcontextprotocol/sdk/types.js
Normal file
51
src/__mocks__/@modelcontextprotocol/sdk/types.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const CallToolResultSchema = {
|
||||
parse: jest.fn().mockReturnValue({})
|
||||
}
|
||||
|
||||
const ListToolsResultSchema = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
tools: []
|
||||
})
|
||||
}
|
||||
|
||||
const ListResourcesResultSchema = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
resources: []
|
||||
})
|
||||
}
|
||||
|
||||
const ListResourceTemplatesResultSchema = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
resourceTemplates: []
|
||||
})
|
||||
}
|
||||
|
||||
const ReadResourceResultSchema = {
|
||||
parse: jest.fn().mockReturnValue({
|
||||
contents: []
|
||||
})
|
||||
}
|
||||
|
||||
const ErrorCode = {
|
||||
InvalidRequest: 'InvalidRequest',
|
||||
MethodNotFound: 'MethodNotFound',
|
||||
InvalidParams: 'InvalidParams',
|
||||
InternalError: 'InternalError'
|
||||
}
|
||||
|
||||
class McpError extends Error {
|
||||
constructor(code, message) {
|
||||
super(message)
|
||||
this.code = code
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CallToolResultSchema,
|
||||
ListToolsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ReadResourceResultSchema,
|
||||
ErrorCode,
|
||||
McpError
|
||||
}
|
||||
17
src/__mocks__/McpHub.ts
Normal file
17
src/__mocks__/McpHub.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export class McpHub {
|
||||
connections = []
|
||||
isConnecting = false
|
||||
|
||||
constructor() {
|
||||
this.toggleToolAlwaysAllow = jest.fn()
|
||||
this.callTool = jest.fn()
|
||||
}
|
||||
|
||||
async toggleToolAlwaysAllow(serverName: string, toolName: string, shouldAllow: boolean): Promise<void> {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
async callTool(serverName: string, toolName: string, toolArguments?: Record<string, unknown>): Promise<any> {
|
||||
return Promise.resolve({ result: 'success' })
|
||||
}
|
||||
}
|
||||
12
src/__mocks__/default-shell.js
Normal file
12
src/__mocks__/default-shell.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// Mock default shell based on platform
|
||||
const os = require('os');
|
||||
|
||||
let defaultShell;
|
||||
if (os.platform() === 'win32') {
|
||||
defaultShell = 'cmd.exe';
|
||||
} else {
|
||||
defaultShell = '/bin/bash';
|
||||
}
|
||||
|
||||
module.exports = defaultShell;
|
||||
module.exports.default = defaultShell;
|
||||
6
src/__mocks__/delay.js
Normal file
6
src/__mocks__/delay.js
Normal file
@@ -0,0 +1,6 @@
|
||||
function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
module.exports = delay;
|
||||
module.exports.default = delay;
|
||||
10
src/__mocks__/globby.js
Normal file
10
src/__mocks__/globby.js
Normal file
@@ -0,0 +1,10 @@
|
||||
function globby(patterns, options) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
globby.sync = function(patterns, options) {
|
||||
return [];
|
||||
};
|
||||
|
||||
module.exports = globby;
|
||||
module.exports.default = globby;
|
||||
6
src/__mocks__/os-name.js
Normal file
6
src/__mocks__/os-name.js
Normal file
@@ -0,0 +1,6 @@
|
||||
function osName() {
|
||||
return 'macOS';
|
||||
}
|
||||
|
||||
module.exports = osName;
|
||||
module.exports.default = osName;
|
||||
20
src/__mocks__/p-wait-for.js
Normal file
20
src/__mocks__/p-wait-for.js
Normal file
@@ -0,0 +1,20 @@
|
||||
function pWaitFor(condition, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const interval = setInterval(() => {
|
||||
if (condition()) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, options.interval || 20);
|
||||
|
||||
if (options.timeout) {
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
reject(new Error('Timed out'));
|
||||
}, options.timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = pWaitFor;
|
||||
module.exports.default = pWaitFor;
|
||||
25
src/__mocks__/serialize-error.js
Normal file
25
src/__mocks__/serialize-error.js
Normal file
@@ -0,0 +1,25 @@
|
||||
function serializeError(error) {
|
||||
if (error instanceof Error) {
|
||||
return {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
};
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
function deserializeError(errorData) {
|
||||
if (errorData && typeof errorData === 'object') {
|
||||
const error = new Error(errorData.message);
|
||||
error.name = errorData.name;
|
||||
error.stack = errorData.stack;
|
||||
return error;
|
||||
}
|
||||
return errorData;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
serializeError,
|
||||
deserializeError
|
||||
};
|
||||
7
src/__mocks__/strip-ansi.js
Normal file
7
src/__mocks__/strip-ansi.js
Normal file
@@ -0,0 +1,7 @@
|
||||
function stripAnsi(string) {
|
||||
// Simple mock that just returns the input string
|
||||
return string;
|
||||
}
|
||||
|
||||
module.exports = stripAnsi;
|
||||
module.exports.default = stripAnsi;
|
||||
57
src/__mocks__/vscode.js
Normal file
57
src/__mocks__/vscode.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const vscode = {
|
||||
window: {
|
||||
showInformationMessage: jest.fn(),
|
||||
showErrorMessage: jest.fn(),
|
||||
createTextEditorDecorationType: jest.fn().mockReturnValue({
|
||||
dispose: jest.fn()
|
||||
})
|
||||
},
|
||||
workspace: {
|
||||
onDidSaveTextDocument: jest.fn()
|
||||
},
|
||||
Disposable: class {
|
||||
dispose() {}
|
||||
},
|
||||
Uri: {
|
||||
file: (path) => ({
|
||||
fsPath: path,
|
||||
scheme: 'file',
|
||||
authority: '',
|
||||
path: path,
|
||||
query: '',
|
||||
fragment: '',
|
||||
with: jest.fn(),
|
||||
toJSON: jest.fn()
|
||||
})
|
||||
},
|
||||
EventEmitter: class {
|
||||
constructor() {
|
||||
this.event = jest.fn();
|
||||
this.fire = jest.fn();
|
||||
}
|
||||
},
|
||||
ConfigurationTarget: {
|
||||
Global: 1,
|
||||
Workspace: 2,
|
||||
WorkspaceFolder: 3
|
||||
},
|
||||
Position: class {
|
||||
constructor(line, character) {
|
||||
this.line = line;
|
||||
this.character = character;
|
||||
}
|
||||
},
|
||||
Range: class {
|
||||
constructor(startLine, startCharacter, endLine, endCharacter) {
|
||||
this.start = new vscode.Position(startLine, startCharacter);
|
||||
this.end = new vscode.Position(endLine, endCharacter);
|
||||
}
|
||||
},
|
||||
ThemeColor: class {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = vscode;
|
||||
@@ -550,6 +550,18 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
break
|
||||
}
|
||||
case "toggleToolAlwaysAllow": {
|
||||
try {
|
||||
await this.mcpHub?.toggleToolAlwaysAllow(
|
||||
message.serverName!,
|
||||
message.toolName!,
|
||||
message.alwaysAllow!
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`Failed to toggle auto-approve for tool ${message.toolName}:`, error)
|
||||
}
|
||||
break
|
||||
}
|
||||
// Add more switch case statements here as more webview message commands
|
||||
// are created within the webview context (i.e. inside media/main.js)
|
||||
case "playSound":
|
||||
|
||||
@@ -33,14 +33,17 @@ export type McpConnection = {
|
||||
}
|
||||
|
||||
// StdioServerParameters
|
||||
const AlwaysAllowSchema = z.array(z.string()).default([])
|
||||
|
||||
const StdioConfigSchema = z.object({
|
||||
command: z.string(),
|
||||
args: z.array(z.string()).optional(),
|
||||
env: z.record(z.string()).optional(),
|
||||
alwaysAllow: AlwaysAllowSchema.optional()
|
||||
})
|
||||
|
||||
const McpSettingsSchema = z.object({
|
||||
mcpServers: z.record(StdioConfigSchema),
|
||||
mcpServers: z.record(StdioConfigSchema)
|
||||
})
|
||||
|
||||
export class McpHub {
|
||||
@@ -285,7 +288,21 @@ export class McpHub {
|
||||
const response = await this.connections
|
||||
.find((conn) => conn.server.name === serverName)
|
||||
?.client.request({ method: "tools/list" }, ListToolsResultSchema)
|
||||
return response?.tools || []
|
||||
|
||||
// Get always allow settings
|
||||
const settingsPath = await this.getMcpSettingsFilePath()
|
||||
const content = await fs.readFile(settingsPath, "utf-8")
|
||||
const config = JSON.parse(content)
|
||||
const alwaysAllowConfig = config.mcpServers[serverName]?.alwaysAllow || []
|
||||
|
||||
// Mark tools as always allowed based on settings
|
||||
const tools = (response?.tools || []).map(tool => ({
|
||||
...tool,
|
||||
alwaysAllow: alwaysAllowConfig.includes(tool.name)
|
||||
}))
|
||||
|
||||
console.log(`[MCP] Fetched tools for ${serverName}:`, tools)
|
||||
return tools
|
||||
} catch (error) {
|
||||
// console.error(`Failed to fetch tools for ${serverName}:`, error)
|
||||
return []
|
||||
@@ -478,6 +495,7 @@ export class McpHub {
|
||||
`No connection found for server: ${serverName}. Please make sure to use MCP servers available under 'Connected MCP Servers'.`,
|
||||
)
|
||||
}
|
||||
|
||||
return await connection.client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
@@ -490,6 +508,45 @@ export class McpHub {
|
||||
)
|
||||
}
|
||||
|
||||
async toggleToolAlwaysAllow(serverName: string, toolName: string, shouldAllow: boolean): Promise<void> {
|
||||
try {
|
||||
const settingsPath = await this.getMcpSettingsFilePath()
|
||||
const content = await fs.readFile(settingsPath, "utf-8")
|
||||
const config = JSON.parse(content)
|
||||
|
||||
// Initialize alwaysAllow if it doesn't exist
|
||||
if (!config.mcpServers[serverName].alwaysAllow) {
|
||||
config.mcpServers[serverName].alwaysAllow = []
|
||||
}
|
||||
|
||||
const alwaysAllow = config.mcpServers[serverName].alwaysAllow
|
||||
const toolIndex = alwaysAllow.indexOf(toolName)
|
||||
|
||||
if (shouldAllow && toolIndex === -1) {
|
||||
// Add tool to always allow list
|
||||
alwaysAllow.push(toolName)
|
||||
} else if (!shouldAllow && toolIndex !== -1) {
|
||||
// Remove tool from always allow list
|
||||
alwaysAllow.splice(toolIndex, 1)
|
||||
}
|
||||
|
||||
// Write updated config back to file
|
||||
await fs.writeFile(settingsPath, JSON.stringify(config, null, 2))
|
||||
|
||||
// Update the tools list to reflect the change
|
||||
const connection = this.connections.find(conn => conn.server.name === serverName)
|
||||
if (connection) {
|
||||
connection.server.tools = await this.fetchToolsList(serverName)
|
||||
await this.notifyWebviewOfServerChanges()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Failed to update always allow settings:", error)
|
||||
vscode.window.showErrorMessage("Failed to update always allow settings")
|
||||
throw error // Re-throw to ensure the error is properly handled
|
||||
}
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
this.removeAllFileWatchers()
|
||||
for (const connection of this.connections) {
|
||||
|
||||
193
src/services/mcp/__tests__/McpHub.test.ts
Normal file
193
src/services/mcp/__tests__/McpHub.test.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import type { McpHub as McpHubType } from '../McpHub'
|
||||
import type { ClineProvider } from '../../../core/webview/ClineProvider'
|
||||
import type { ExtensionContext, Uri } from 'vscode'
|
||||
import type { McpConnection } from '../McpHub'
|
||||
|
||||
const vscode = require('vscode')
|
||||
const fs = require('fs/promises')
|
||||
const { McpHub } = require('../McpHub')
|
||||
|
||||
jest.mock('vscode')
|
||||
jest.mock('fs/promises')
|
||||
jest.mock('../../../core/webview/ClineProvider')
|
||||
|
||||
describe('McpHub', () => {
|
||||
let mcpHub: McpHubType
|
||||
let mockProvider: Partial<ClineProvider>
|
||||
const mockSettingsPath = '/mock/settings/path/cline_mcp_settings.json'
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
const mockUri: Uri = {
|
||||
scheme: 'file',
|
||||
authority: '',
|
||||
path: '/test/path',
|
||||
query: '',
|
||||
fragment: '',
|
||||
fsPath: '/test/path',
|
||||
with: jest.fn(),
|
||||
toJSON: jest.fn()
|
||||
}
|
||||
|
||||
mockProvider = {
|
||||
ensureSettingsDirectoryExists: jest.fn().mockResolvedValue('/mock/settings/path'),
|
||||
ensureMcpServersDirectoryExists: jest.fn().mockResolvedValue('/mock/settings/path'),
|
||||
postMessageToWebview: jest.fn(),
|
||||
context: {
|
||||
subscriptions: [],
|
||||
workspaceState: {} as any,
|
||||
globalState: {} as any,
|
||||
secrets: {} as any,
|
||||
extensionUri: mockUri,
|
||||
extensionPath: '/test/path',
|
||||
storagePath: '/test/storage',
|
||||
globalStoragePath: '/test/global-storage',
|
||||
environmentVariableCollection: {} as any,
|
||||
extension: {
|
||||
id: 'test-extension',
|
||||
extensionUri: mockUri,
|
||||
extensionPath: '/test/path',
|
||||
extensionKind: 1,
|
||||
isActive: true,
|
||||
packageJSON: {
|
||||
version: '1.0.0'
|
||||
},
|
||||
activate: jest.fn(),
|
||||
exports: undefined
|
||||
} as any,
|
||||
asAbsolutePath: (path: string) => path,
|
||||
storageUri: mockUri,
|
||||
globalStorageUri: mockUri,
|
||||
logUri: mockUri,
|
||||
extensionMode: 1,
|
||||
logPath: '/test/path',
|
||||
languageModelAccessInformation: {} as any
|
||||
} as ExtensionContext
|
||||
}
|
||||
|
||||
// Mock fs.readFile for initial settings
|
||||
;(fs.readFile as jest.Mock).mockResolvedValue(JSON.stringify({
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js'],
|
||||
alwaysAllow: ['allowed-tool']
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
mcpHub = new McpHub(mockProvider as ClineProvider)
|
||||
})
|
||||
|
||||
describe('toggleToolAlwaysAllow', () => {
|
||||
it('should add tool to always allow list when enabling', async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js'],
|
||||
alwaysAllow: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
|
||||
await mcpHub.toggleToolAlwaysAllow('test-server', 'new-tool', true)
|
||||
|
||||
// Verify the config was updated correctly
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers['test-server'].alwaysAllow).toContain('new-tool')
|
||||
})
|
||||
|
||||
it('should remove tool from always allow list when disabling', async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js'],
|
||||
alwaysAllow: ['existing-tool']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
|
||||
await mcpHub.toggleToolAlwaysAllow('test-server', 'existing-tool', false)
|
||||
|
||||
// Verify the config was updated correctly
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers['test-server'].alwaysAllow).not.toContain('existing-tool')
|
||||
})
|
||||
|
||||
it('should initialize alwaysAllow if it does not exist', async () => {
|
||||
const mockConfig = {
|
||||
mcpServers: {
|
||||
'test-server': {
|
||||
command: 'node',
|
||||
args: ['test.js']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock reading initial config
|
||||
;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig))
|
||||
|
||||
await mcpHub.toggleToolAlwaysAllow('test-server', 'new-tool', true)
|
||||
|
||||
// Verify the config was updated with initialized alwaysAllow
|
||||
const writeCall = (fs.writeFile as jest.Mock).mock.calls[0]
|
||||
const writtenConfig = JSON.parse(writeCall[1])
|
||||
expect(writtenConfig.mcpServers['test-server'].alwaysAllow).toBeDefined()
|
||||
expect(writtenConfig.mcpServers['test-server'].alwaysAllow).toContain('new-tool')
|
||||
})
|
||||
})
|
||||
|
||||
describe('callTool', () => {
|
||||
it('should execute tool successfully', async () => {
|
||||
// Mock the connection with a minimal client implementation
|
||||
const mockConnection: McpConnection = {
|
||||
server: {
|
||||
name: 'test-server',
|
||||
config: JSON.stringify({}),
|
||||
status: 'connected' as const
|
||||
},
|
||||
client: {
|
||||
request: jest.fn().mockResolvedValue({ result: 'success' })
|
||||
} as any,
|
||||
transport: {
|
||||
start: jest.fn(),
|
||||
close: jest.fn(),
|
||||
stderr: { on: jest.fn() }
|
||||
} as any
|
||||
}
|
||||
|
||||
mcpHub.connections = [mockConnection]
|
||||
|
||||
await mcpHub.callTool('test-server', 'some-tool', {})
|
||||
|
||||
// Verify the request was made with correct parameters
|
||||
expect(mockConnection.client.request).toHaveBeenCalledWith(
|
||||
{
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: 'some-tool',
|
||||
arguments: {}
|
||||
}
|
||||
},
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw error if server not found', async () => {
|
||||
await expect(mcpHub.callTool('non-existent-server', 'some-tool', {}))
|
||||
.rejects
|
||||
.toThrow('No connection found for server: non-existent-server')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -34,6 +34,7 @@ export interface WebviewMessage {
|
||||
| "diffEnabled"
|
||||
| "openMcpSettings"
|
||||
| "restartMcpServer"
|
||||
| "toggleToolAlwaysAllow"
|
||||
text?: string
|
||||
askResponse?: ClineAskResponse
|
||||
apiConfiguration?: ApiConfiguration
|
||||
@@ -41,6 +42,10 @@ export interface WebviewMessage {
|
||||
bool?: boolean
|
||||
commands?: string[]
|
||||
audioType?: AudioType
|
||||
// For toggleToolAutoApprove
|
||||
serverName?: string
|
||||
toolName?: string
|
||||
alwaysAllow?: boolean
|
||||
}
|
||||
|
||||
export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
|
||||
|
||||
@@ -12,6 +12,7 @@ export type McpTool = {
|
||||
name: string
|
||||
description?: string
|
||||
inputSchema?: object
|
||||
alwaysAllow?: boolean
|
||||
}
|
||||
|
||||
export type McpResource = {
|
||||
|
||||
Reference in New Issue
Block a user