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:
Etienne
2026-06-03 18:58:32 +02:00
committed by GitHub
parent 4b15b949f3
commit dd0039ca1c
20 changed files with 313 additions and 96 deletions
@@ -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."""