feat: implement AI-optimized MCP tools with 95% size reduction
- Add get_node_essentials tool for 10-20 essential properties only - Add search_node_properties for targeted property search - Add get_node_for_task with 14 pre-configured templates - Add validate_node_config for comprehensive validation - Add get_property_dependencies for visibility analysis - Implement PropertyFilter service with curated essentials - Implement ExampleGenerator with working examples - Implement TaskTemplates for common workflows - Implement ConfigValidator with security checks - Implement PropertyDependencies for dependency analysis - Enhance property descriptions to 100% coverage - Add version information to essentials response - Update documentation with new tools Response sizes reduced from 100KB+ to <5KB for better AI agent usability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
388
docs/MCP_QUICK_START_GUIDE.md
Normal file
388
docs/MCP_QUICK_START_GUIDE.md
Normal file
@@ -0,0 +1,388 @@
|
||||
# MCP Implementation Quick Start Guide
|
||||
|
||||
## Immediate Actions (Day 1)
|
||||
|
||||
### 1. Create Essential Properties Configuration
|
||||
|
||||
Create `src/data/essential-properties.json`:
|
||||
```json
|
||||
{
|
||||
"nodes-base.httpRequest": {
|
||||
"required": ["url"],
|
||||
"common": ["method", "authentication", "sendBody", "contentType", "sendHeaders"],
|
||||
"examples": {
|
||||
"minimal": {
|
||||
"url": "https://api.example.com/data"
|
||||
},
|
||||
"getWithAuth": {
|
||||
"method": "GET",
|
||||
"url": "https://api.example.com/protected",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "headerAuth"
|
||||
},
|
||||
"postJson": {
|
||||
"method": "POST",
|
||||
"url": "https://api.example.com/create",
|
||||
"sendBody": true,
|
||||
"contentType": "json",
|
||||
"jsonBody": "{ \"name\": \"example\" }"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nodes-base.webhook": {
|
||||
"required": [],
|
||||
"common": ["path", "method", "responseMode", "responseData"],
|
||||
"examples": {
|
||||
"minimal": {
|
||||
"path": "webhook",
|
||||
"method": "POST"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Implement get_node_essentials Tool
|
||||
|
||||
Add to `src/mcp/server.ts`:
|
||||
|
||||
```typescript
|
||||
// Add to tool implementations
|
||||
case "get_node_essentials": {
|
||||
const { nodeType } = request.params.arguments as { nodeType: string };
|
||||
|
||||
// Load essential properties config
|
||||
const essentialsConfig = require('../data/essential-properties.json');
|
||||
const nodeConfig = essentialsConfig[nodeType];
|
||||
|
||||
if (!nodeConfig) {
|
||||
// Fallback: extract from existing data
|
||||
const node = await service.getNodeByType(nodeType);
|
||||
if (!node) {
|
||||
return { error: `Node type ${nodeType} not found` };
|
||||
}
|
||||
|
||||
// Parse properties to find required ones
|
||||
const properties = JSON.parse(node.properties_schema || '[]');
|
||||
const required = properties.filter((p: any) => p.required);
|
||||
const common = properties.slice(0, 5); // Top 5 as fallback
|
||||
|
||||
return {
|
||||
nodeType,
|
||||
displayName: node.display_name,
|
||||
description: node.description,
|
||||
requiredProperties: required.map(simplifyProperty),
|
||||
commonProperties: common.map(simplifyProperty),
|
||||
examples: {
|
||||
minimal: {},
|
||||
common: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Use configured essentials
|
||||
const node = await service.getNodeByType(nodeType);
|
||||
const properties = JSON.parse(node.properties_schema || '[]');
|
||||
|
||||
const requiredProps = nodeConfig.required.map((name: string) => {
|
||||
const prop = findPropertyByName(properties, name);
|
||||
return prop ? simplifyProperty(prop) : null;
|
||||
}).filter(Boolean);
|
||||
|
||||
const commonProps = nodeConfig.common.map((name: string) => {
|
||||
const prop = findPropertyByName(properties, name);
|
||||
return prop ? simplifyProperty(prop) : null;
|
||||
}).filter(Boolean);
|
||||
|
||||
return {
|
||||
nodeType,
|
||||
displayName: node.display_name,
|
||||
description: node.description,
|
||||
requiredProperties: requiredProps,
|
||||
commonProperties: commonProps,
|
||||
examples: nodeConfig.examples || {}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function simplifyProperty(prop: any) {
|
||||
return {
|
||||
name: prop.name,
|
||||
type: prop.type,
|
||||
description: prop.description || prop.displayName || '',
|
||||
default: prop.default,
|
||||
options: prop.options?.map((opt: any) =>
|
||||
typeof opt === 'string' ? opt : opt.value
|
||||
),
|
||||
placeholder: prop.placeholder
|
||||
};
|
||||
}
|
||||
|
||||
function findPropertyByName(properties: any[], name: string): any {
|
||||
for (const prop of properties) {
|
||||
if (prop.name === name) return prop;
|
||||
// Check in nested collections
|
||||
if (prop.type === 'collection' && prop.options) {
|
||||
const found = findPropertyByName(prop.options, name);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Add Tool Definition
|
||||
|
||||
Add to tool definitions:
|
||||
|
||||
```typescript
|
||||
{
|
||||
name: "get_node_essentials",
|
||||
description: "Get only essential and commonly-used properties for a node - perfect for quick configuration",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
nodeType: {
|
||||
type: "string",
|
||||
description: "The node type (e.g., 'nodes-base.httpRequest')"
|
||||
}
|
||||
},
|
||||
required: ["nodeType"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Property Parser Service
|
||||
|
||||
Create `src/services/property-parser.ts`:
|
||||
|
||||
```typescript
|
||||
export class PropertyParser {
|
||||
/**
|
||||
* Parse nested properties and flatten to searchable format
|
||||
*/
|
||||
static parseProperties(properties: any[], path = ''): ParsedProperty[] {
|
||||
const results: ParsedProperty[] = [];
|
||||
|
||||
for (const prop of properties) {
|
||||
const currentPath = path ? `${path}.${prop.name}` : prop.name;
|
||||
|
||||
// Add current property
|
||||
results.push({
|
||||
name: prop.name,
|
||||
path: currentPath,
|
||||
type: prop.type,
|
||||
description: prop.description || prop.displayName || '',
|
||||
required: prop.required || false,
|
||||
displayConditions: prop.displayOptions,
|
||||
default: prop.default,
|
||||
options: prop.options?.filter((opt: any) => typeof opt === 'string' || opt.value)
|
||||
});
|
||||
|
||||
// Recursively parse nested properties
|
||||
if (prop.type === 'collection' && prop.options) {
|
||||
results.push(...this.parseProperties(prop.options, currentPath));
|
||||
} else if (prop.type === 'fixedCollection' && prop.options) {
|
||||
for (const option of prop.options) {
|
||||
if (option.values) {
|
||||
results.push(...this.parseProperties(option.values, `${currentPath}.${option.name}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find properties matching a search query
|
||||
*/
|
||||
static searchProperties(properties: ParsedProperty[], query: string): ParsedProperty[] {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
return properties.filter(prop =>
|
||||
prop.name.toLowerCase().includes(lowerQuery) ||
|
||||
prop.description.toLowerCase().includes(lowerQuery) ||
|
||||
prop.path.toLowerCase().includes(lowerQuery)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize properties
|
||||
*/
|
||||
static categorizeProperties(properties: ParsedProperty[]): CategorizedProperties {
|
||||
const categories: CategorizedProperties = {
|
||||
authentication: [],
|
||||
request: [],
|
||||
response: [],
|
||||
advanced: [],
|
||||
other: []
|
||||
};
|
||||
|
||||
for (const prop of properties) {
|
||||
if (prop.name.includes('auth') || prop.name.includes('credential')) {
|
||||
categories.authentication.push(prop);
|
||||
} else if (prop.name.includes('body') || prop.name.includes('header') ||
|
||||
prop.name.includes('query') || prop.name.includes('url')) {
|
||||
categories.request.push(prop);
|
||||
} else if (prop.name.includes('response') || prop.name.includes('output')) {
|
||||
categories.response.push(prop);
|
||||
} else if (prop.path.includes('options.')) {
|
||||
categories.advanced.push(prop);
|
||||
} else {
|
||||
categories.other.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
}
|
||||
|
||||
interface ParsedProperty {
|
||||
name: string;
|
||||
path: string;
|
||||
type: string;
|
||||
description: string;
|
||||
required: boolean;
|
||||
displayConditions?: any;
|
||||
default?: any;
|
||||
options?: any[];
|
||||
}
|
||||
|
||||
interface CategorizedProperties {
|
||||
authentication: ParsedProperty[];
|
||||
request: ParsedProperty[];
|
||||
response: ParsedProperty[];
|
||||
advanced: ParsedProperty[];
|
||||
other: ParsedProperty[];
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Quick Test Script
|
||||
|
||||
Create `scripts/test-essentials.ts`:
|
||||
|
||||
```typescript
|
||||
import { MCPClient } from '../src/mcp/client';
|
||||
|
||||
async function testEssentials() {
|
||||
const client = new MCPClient();
|
||||
|
||||
console.log('Testing get_node_essentials...\n');
|
||||
|
||||
// Test HTTP Request node
|
||||
const httpEssentials = await client.call('get_node_essentials', {
|
||||
nodeType: 'nodes-base.httpRequest'
|
||||
});
|
||||
|
||||
console.log('HTTP Request Essentials:');
|
||||
console.log(`- Required: ${httpEssentials.requiredProperties.map(p => p.name).join(', ')}`);
|
||||
console.log(`- Common: ${httpEssentials.commonProperties.map(p => p.name).join(', ')}`);
|
||||
console.log(`- Total properties: ${httpEssentials.requiredProperties.length + httpEssentials.commonProperties.length}`);
|
||||
|
||||
// Compare with full response
|
||||
const fullInfo = await client.call('get_node_info', {
|
||||
nodeType: 'nodes-base.httpRequest'
|
||||
});
|
||||
|
||||
const fullSize = JSON.stringify(fullInfo).length;
|
||||
const essentialSize = JSON.stringify(httpEssentials).length;
|
||||
|
||||
console.log(`\nSize comparison:`);
|
||||
console.log(`- Full response: ${(fullSize / 1024).toFixed(1)}KB`);
|
||||
console.log(`- Essential response: ${(essentialSize / 1024).toFixed(1)}KB`);
|
||||
console.log(`- Reduction: ${((1 - essentialSize / fullSize) * 100).toFixed(1)}%`);
|
||||
}
|
||||
|
||||
testEssentials().catch(console.error);
|
||||
```
|
||||
|
||||
## Day 2-3: Implement search_node_properties
|
||||
|
||||
```typescript
|
||||
case "search_node_properties": {
|
||||
const { nodeType, query } = request.params.arguments as {
|
||||
nodeType: string;
|
||||
query: string;
|
||||
};
|
||||
|
||||
const node = await service.getNodeByType(nodeType);
|
||||
if (!node) {
|
||||
return { error: `Node type ${nodeType} not found` };
|
||||
}
|
||||
|
||||
const properties = JSON.parse(node.properties_schema || '[]');
|
||||
const parsed = PropertyParser.parseProperties(properties);
|
||||
const matches = PropertyParser.searchProperties(parsed, query);
|
||||
|
||||
return {
|
||||
query,
|
||||
matches: matches.map(prop => ({
|
||||
name: prop.name,
|
||||
type: prop.type,
|
||||
path: prop.path,
|
||||
description: prop.description,
|
||||
visibleWhen: prop.displayConditions?.show
|
||||
})),
|
||||
totalMatches: matches.length
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Day 4-5: Implement get_node_for_task
|
||||
|
||||
Create `src/data/task-templates.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"post_json_request": {
|
||||
"description": "Make a POST request with JSON data",
|
||||
"nodeType": "nodes-base.httpRequest",
|
||||
"configuration": {
|
||||
"method": "POST",
|
||||
"url": "",
|
||||
"sendBody": true,
|
||||
"contentType": "json",
|
||||
"specifyBody": "json",
|
||||
"jsonBody": ""
|
||||
},
|
||||
"userMustProvide": [
|
||||
{ "property": "url", "description": "API endpoint URL" },
|
||||
{ "property": "jsonBody", "description": "JSON data to send" }
|
||||
],
|
||||
"optionalEnhancements": [
|
||||
{ "property": "authentication", "description": "Add authentication if required" },
|
||||
{ "property": "sendHeaders", "description": "Add custom headers" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Test get_node_essentials with HTTP Request node
|
||||
- [ ] Verify size reduction is >90%
|
||||
- [ ] Test with Webhook, Agent, and Code nodes
|
||||
- [ ] Validate examples work correctly
|
||||
- [ ] Test property search functionality
|
||||
- [ ] Verify task templates are valid
|
||||
- [ ] Check backward compatibility
|
||||
- [ ] Measure response times (<100ms)
|
||||
|
||||
## Success Indicators
|
||||
|
||||
1. **Immediate (Day 1)**:
|
||||
- get_node_essentials returns <5KB for HTTP Request
|
||||
- Response includes working examples
|
||||
- No errors with top 10 nodes
|
||||
|
||||
2. **Week 1**:
|
||||
- 90% reduction in response size
|
||||
- Property search working
|
||||
- 5+ task templates created
|
||||
- Positive AI agent feedback
|
||||
|
||||
3. **Month 1**:
|
||||
- All tools implemented
|
||||
- 50+ nodes optimized
|
||||
- Configuration time <1 minute
|
||||
- Error rate <10%
|
||||
Reference in New Issue
Block a user