feat(mcp) - optimize instruction prompt and hide get_tool_catalog (#21183)
Workspace-aware initialize.instructions - Deleted the static mcp-server-instructions.const.ts - Created build-mcp-server-instructions.util.ts — a comprehensive system prompt with identity, object list, tool grammar, routing decision tree, intent mapping, skills vs tools, safety constraints, and data efficiency guidelines - Created McpInstructionBuilderService — fetches workspace-specific object names + skill names and injects them into the instructions Hide/deprecate get_tool_catalog Benefit : skip first MCP call (tools are included in instruction)
This commit is contained in:
+4
-10
@@ -4,7 +4,7 @@ import {
|
||||
type ObjectsPermissions,
|
||||
type ObjectsPermissionsByRoleId,
|
||||
} from 'twenty-shared/types';
|
||||
import { camelToSnakeCase, isDefined } from 'twenty-shared/utils';
|
||||
import { camelToSnakeCase } from 'twenty-shared/utils';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import { type ToolDescriptor } from 'src/engine/core-modules/tool-provider/types/tool-descriptor.type';
|
||||
import { type ToolIndexEntry } from 'src/engine/core-modules/tool-provider/types/tool-index-entry.type';
|
||||
import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
|
||||
import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/ai/ai-agent/utils/is-workflow-related-object.util';
|
||||
import { getDatabaseCrudToolFlatObjects } from 'src/engine/metadata-modules/ai/ai-agent/utils/get-database-crud-tool-flat-objects.util';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { computePermissionIntersection } from 'src/engine/twenty-orm/utils/compute-permission-intersection.util';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
@@ -90,17 +90,11 @@ export class DatabaseToolProvider implements ToolProvider {
|
||||
},
|
||||
);
|
||||
|
||||
const allFlatObjects = Object.values(
|
||||
const allFlatObjects = getDatabaseCrudToolFlatObjects(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((obj) => obj.isActive);
|
||||
);
|
||||
|
||||
for (const flatObject of allFlatObjects) {
|
||||
if (isWorkflowRelatedObject(flatObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const permission = objectPermissions[flatObject.id];
|
||||
|
||||
if (!permission) {
|
||||
|
||||
+3
-3
@@ -7,9 +7,9 @@ import { Repository } from 'typeorm';
|
||||
|
||||
import { type ObjectRecordGroupBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type';
|
||||
import { fromUserEntityToFlat } from 'src/engine/core-modules/user/utils/from-user-entity-to-flat.util';
|
||||
import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider-context.type';
|
||||
import { fromUserEntityToFlat } from 'src/engine/core-modules/user/utils/from-user-entity-to-flat.util';
|
||||
import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
@@ -23,9 +23,9 @@ import { CreateRecordService } from 'src/engine/core-modules/record-crud/service
|
||||
import { DeleteRecordService } from 'src/engine/core-modules/record-crud/services/delete-record.service';
|
||||
import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service';
|
||||
import { GroupByRecordsService } from 'src/engine/core-modules/record-crud/services/group-by-records.service';
|
||||
import { type FindRecordsParams } from 'src/engine/core-modules/record-crud/types/find-records-params.type';
|
||||
import { UpdateManyRecordsService } from 'src/engine/core-modules/record-crud/services/update-many-records.service';
|
||||
import { UpdateRecordService } from 'src/engine/core-modules/record-crud/services/update-record.service';
|
||||
import { type FindRecordsParams } from 'src/engine/core-modules/record-crud/types/find-records-params.type';
|
||||
import { TOOL_PROVIDERS } from 'src/engine/core-modules/tool-provider/constants/tool-providers.token';
|
||||
import { type ToolProvider } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider.interface';
|
||||
import { type ToolDescriptor } from 'src/engine/core-modules/tool-provider/types/tool-descriptor.type';
|
||||
|
||||
+3
-5
@@ -9,9 +9,7 @@ import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.
|
||||
export const EXECUTE_TOOL_TOOL_NAME = 'execute_tool';
|
||||
|
||||
const executeToolInputZodSchema = z.object({
|
||||
toolName: z
|
||||
.string()
|
||||
.describe('Exact tool name from get_tool_catalog. Do not guess.'),
|
||||
toolName: z.string().describe('Exact tool name. Do not guess.'),
|
||||
arguments: z
|
||||
.record(z.string(), z.unknown())
|
||||
.describe('Arguments matching the schema returned by learn_tools.'),
|
||||
@@ -53,7 +51,7 @@ export const createExecuteToolTool = (
|
||||
},
|
||||
) => ({
|
||||
description:
|
||||
'STEP 3: Execute a tool by name with arguments. You MUST call get_tool_catalog (step 1) and learn_tools (step 2) first to discover the tool name and its required input schema.',
|
||||
'Execute a tool by name with arguments. Call learn_tools first to discover the required input schema.',
|
||||
inputSchema: executeToolInputSchema,
|
||||
execute: async (parameters: ExecuteToolInput): Promise<ToolOutput> => {
|
||||
const { toolName, arguments: args = {} } = parameters;
|
||||
@@ -62,7 +60,7 @@ export const createExecuteToolTool = (
|
||||
return {
|
||||
success: false,
|
||||
message: `Tool "${toolName}" is not available`,
|
||||
error: `Tool "${toolName}" is not available in this context. Use get_tool_catalog to see which tools are available.`,
|
||||
error: `Tool "${toolName}" is not available in this context. Use learn_tools to discover available tools.`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -12,9 +12,7 @@ export type LearnToolsAspect = z.infer<typeof learnToolsAspectSchema>;
|
||||
export const learnToolsInputSchema = z.object({
|
||||
toolNames: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
'Exact tool names from get_tool_catalog. Do not guess tool names.',
|
||||
),
|
||||
.describe('Exact tool names. Do not guess tool names.'),
|
||||
aspects: z
|
||||
.array(learnToolsAspectSchema)
|
||||
.optional()
|
||||
@@ -42,7 +40,7 @@ export const createLearnToolsTool = (
|
||||
excludeTools?: Set<string>,
|
||||
) => ({
|
||||
description:
|
||||
'STEP 2: Get input schemas for tools discovered via get_tool_catalog. Call this with exact tool names to learn the required arguments before calling execute_tool.',
|
||||
'Get input schemas for tools. Call this with exact tool names to learn the required arguments before calling execute_tool.',
|
||||
inputSchema: learnToolsInputSchema,
|
||||
execute: async (parameters: LearnToolsInput): Promise<LearnToolsResult> => {
|
||||
const { toolNames, aspects } = parameters;
|
||||
|
||||
+2
-31
@@ -14,8 +14,8 @@ class TwentyMCP:
|
||||
|
||||
Two categories of tools exist behind /mcp:
|
||||
|
||||
- MCP-native: execute_tool, learn_tools, load_skills, get_tool_catalog,
|
||||
search_help_center. These are the 5 surfaces exposed directly.
|
||||
- MCP-native: execute_tool, learn_tools, load_skills,
|
||||
search_help_center. These are the 4 surfaces exposed directly.
|
||||
|
||||
- Workspace catalog: 250+ CRUD / view / workflow / dashboard tools
|
||||
like find_companies, create_person, update_opportunity. These are
|
||||
@@ -30,7 +30,6 @@ class TwentyMCP:
|
||||
'execute_tool',
|
||||
'learn_tools',
|
||||
'load_skills',
|
||||
'get_tool_catalog',
|
||||
'search_help_center',
|
||||
})
|
||||
|
||||
@@ -86,34 +85,6 @@ class TwentyMCP:
|
||||
return wrapped['result']
|
||||
return wrapped
|
||||
|
||||
def list_tools(self):
|
||||
"""
|
||||
List all workspace catalog tools (250+), not the 5 MCP meta-tools.
|
||||
|
||||
Use call_tool(name, args) to invoke any of them — routing is
|
||||
handled for you.
|
||||
|
||||
Returns:
|
||||
Flat list of tool entries, each with name, description, and
|
||||
category. get_tool_catalog groups by category internally; we
|
||||
flatten for ergonomics.
|
||||
"""
|
||||
catalog = self.call_tool('get_tool_catalog', {})
|
||||
# get_tool_catalog returns { 'catalog': { '<category>': [tools...] } }
|
||||
# Flatten to a single list; preserve the category as a per-tool field
|
||||
# so the docstring's promise holds and consumers don't have to re-call
|
||||
# the catalog. Leave untouched if the shape is unexpected.
|
||||
if isinstance(catalog, dict):
|
||||
grouped = catalog.get('catalog', catalog)
|
||||
if isinstance(grouped, dict):
|
||||
return [
|
||||
({**tool, 'category': category}
|
||||
if isinstance(tool, dict) and 'category' not in tool
|
||||
else tool)
|
||||
for category, tools in grouped.items()
|
||||
for tool in tools
|
||||
]
|
||||
return catalog
|
||||
|
||||
def _raw_mcp_call(self, name: str, arguments: dict = None):
|
||||
"""Low-level: issue a tools/call against the MCP surface verbatim."""
|
||||
|
||||
Reference in New Issue
Block a user