nodesUpdateNodeSubInterface
Modify sub-interface settings like IP addresses, VLAN IDs, descriptions, and annotations on network nodes within Hyperfabric infrastructure.
Instructions
Update a specific sub-interface.
To use this tool, pass the resource ID and the fields to update as arguments
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| fabricId | Yes | This is a read-only field. The unique identifier of the fabric to which this sub-interface belongs to. | |
| nodeId | Yes | This is a read-only field. The unique identifier of the node to which this sub-interface belongs to. | |
| subInterfaceId | Yes | The sub-interface id or name. | |
| annotations | No | A list of name-value annotations to store user-defined data including complex data such as JSON associated with the sub-interface. | |
| description | No | The description is a user-defined field to store notes about the sub-interface. | |
| enabled | No | The enabled state of the sub-interface which indicates if the sub-interface is enabled or disabled. | |
| id | No | This is a read-only field. The unique identifier of the sub-interface of the node in the fabric. | |
| ipv4Addresses | No | A list of up to two IPv4 host addresses with subnet mask to be configured on the sub-interface. | |
| ipv6Addresses | No | A list of up to two IPv6 host addresses with subnet mask to be configured on the sub-interface. | |
| labels | No | A list of user-defined labels that can be used for grouping and filtering sub-interfaces. | |
| metadata | No | Metadata defines a map of attributes related to the lifecycle of the object. | |
| name | No | The name of the sub-interface of the node. Must be in the format Ethernet1_1_1.\<VLAN_ID\>. | |
| parent | No | This is a read-only field. The name of parent interface to which this sub-interface belong to. | |
| vlanId | No | The VLAN ID used as dot1q encapsulation for the sub-interface. The VLAN ID must be between 1 and 4094. | |
| vrfId | No | The unique identifier of the VRF to which this sub-interface is attached to. |
Implementation Reference
- src/main.ts:337-448 (handler)This is the core handler function that executes ALL MCP tools, including 'nodesUpdateNodeSubInterface'. It matches the tool name to an OpenAPI operation, builds the HTTP request (URL, params, body), and calls the Hyperfabric API via axios. The tool logic is generic based on the OpenAPI spec.private async executeApiCall(toolName: string, args: any): Promise<any> { // Validate inputs for port configuration tools to prevent misuse if (toolName === 'nodesSetPorts' || toolName === 'nodesUpdatePort') { // Ensure no executable content or shell commands in arguments const argsStr = JSON.stringify(args); if (/(\$\(|`|eval|exec|system|spawn|child_process)/.test(argsStr)) { throw new Error('Security: Invalid arguments detected. Port configuration tools only accept network settings (speed, MTU, VLAN, etc.)'); } } // Extract the original method and path from the tool name // This is a simplified approach - in a production system you'd want a more robust mapping if (!this.openApiSpec?.paths) { throw new Error("OpenAPI spec not loaded"); } // Find the corresponding operation let foundOperation: { method: string; path: string; operation: OpenAPIOperation } | null = null; for (const [pathKey, pathItem] of Object.entries(this.openApiSpec.paths)) { for (const [method, operation] of Object.entries(pathItem)) { if (typeof operation !== 'object' || !operation) continue; const op = operation as OpenAPIOperation; const expectedToolName = this.generateToolName(method, pathKey, op); if (expectedToolName === toolName) { foundOperation = { method, path: pathKey, operation: op }; break; } } if (foundOperation) break; } if (!foundOperation) { throw new Error(`No operation found for tool: ${toolName}`); } // Build the URL by replacing path parameters let url = foundOperation.path; const queryParams: Record<string, string> = {}; // Handle path and query parameters if (foundOperation.operation.parameters) { for (const param of foundOperation.operation.parameters) { const value = args[param.name]; if (value !== undefined) { if (param.in === 'path') { url = url.replace(`{${param.name}}`, encodeURIComponent(value)); } else if (param.in === 'query') { queryParams[param.name] = value; } } } } // Prepare the request const requestConfig: any = { method: foundOperation.method.toUpperCase(), url, params: queryParams, }; // Handle request body for POST/PUT/PATCH if (['post', 'put', 'patch'].includes(foundOperation.method.toLowerCase())) { // Check if args has a requestBody property (legacy format) if (args.requestBody) { requestConfig.data = args.requestBody; } else { // Build request body from exposed properties // This handles cases where schema properties are exposed directly (e.g., fabrics, nodes, etc.) const requestBody: Record<string, any> = {}; const pathItem = this.openApiSpec?.paths?.[foundOperation.path]; const operation = (pathItem as any)?.[foundOperation.method]; if (operation?.requestBody) { const requestBodyDef = this.resolveSchemaRef(operation.requestBody); const schema = this.deepResolveSchema(requestBodyDef.content?.['application/json']?.schema); // Collect all properties that are part of the request body schema if (schema?.properties) { for (const propName of Object.keys(schema.properties)) { if (args.hasOwnProperty(propName)) { requestBody[propName] = args[propName]; } } } } if (Object.keys(requestBody).length > 0) { requestConfig.data = requestBody; } } } logger.debug(`Making API call: ${requestConfig.method} ${url}`); try { const response = await this.httpClient.request(requestConfig); return { status: response.status, statusText: response.statusText, data: response.data }; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`API call failed: ${error.response?.status} ${error.response?.statusText} - ${JSON.stringify(error.response?.data)}`); } throw error; } }
- src/main.ts:213-290 (schema)Generates the inputSchema for each tool (including 'nodesUpdateNodeSubInterface') by processing OpenAPI operation parameters (path/query) and requestBody schema properties, resolving $ref references recursively.private createToolFromOperation( name: string, method: string, path: string, operation: OpenAPIOperation ): Tool | null { let description = operation.summary || operation.description || `${method.toUpperCase()} ${path}`; // Add security context for network port configuration operations if (name === 'nodesSetPorts' || name === 'nodesUpdatePort') { description += '\n\n[SAFE OPERATION] This tool configures network fabric port settings (speed, MTU, VLAN, etc.) via REST API. It does NOT execute code or commands on the system.'; } // Enhance description for create/update operations if (['post', 'put', 'patch'].includes(method.toLowerCase())) { if (method.toLowerCase() === 'post') { description += '\n\nTo use this tool, pass the required fields as direct arguments (e.g., fabrics=[{name:"my-fabric", description:"...", ...}])'; } else if (method.toLowerCase() === 'put') { description += '\n\nTo use this tool, pass the resource ID and the fields to update as arguments'; } else if (method.toLowerCase() === 'patch') { description += '\n\nTo use this tool, pass the resource ID and the fields to patch as arguments'; } } const properties: Record<string, any> = {}; const required: string[] = []; // Process parameters if (operation.parameters) { for (const param of operation.parameters) { if (param.in === 'path' || param.in === 'query') { properties[param.name] = { type: param.schema?.type || 'string', description: param.description || '' }; if (param.required) { required.push(param.name); } } } } // Process request body for POST/PUT/PATCH requests if (operation.requestBody && ['post', 'put', 'patch'].includes(method.toLowerCase())) { // Resolve the requestBody reference if it exists const requestBody = this.resolveSchemaRef(operation.requestBody); const content = requestBody.content; if (content?.['application/json']?.schema) { let schema = content['application/json'].schema; // Deeply resolve schema references schema = this.deepResolveSchema(schema); if (schema.properties) { // Expose the request body properties directly for (const [propName, propSchema] of Object.entries(schema.properties)) { const propDef = propSchema as any; properties[propName] = this.deepResolveSchema(propDef, 0); if (schema.required?.includes(propName)) { required.push(propName); } } } } } return { name, description, inputSchema: { type: 'object', properties, required } }; }
- src/main.ts:293-295 (registration)Registers the listTools handler which returns all generated tools, including 'nodesUpdateNodeSubInterface'.this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: this.tools }; });
- src/main.ts:297-335 (registration)Registers the callTool handler which dispatches to executeApiCall for the specific tool name.this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => { const { name, arguments: args } = request.params; logger.info(`Calling tool: ${name}`); logger.debug(`Tool arguments: ${JSON.stringify(args, null, 2)}`); try { // Find the tool definition const tool = this.tools.find(t => t.name === name); if (!tool) { throw new Error(`Tool ${name} not found`); } // Execute the API call const result = await this.executeApiCall(name, args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } catch (error) { logger.error(`Error executing tool ${name}:`, error); return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } }); }
- src/main.ts:160-169 (helper)Generates tool names from OpenAPI operations. For 'nodesUpdateNodeSubInterface', it likely uses the operationId directly from the spec.private generateToolName(method: string, path: string, operation: OpenAPIOperation): string { if (operation.operationId) { return operation.operationId; } // Generate a name from the method and path const pathParts = path.split('/').filter(part => part && !part.startsWith('{')); const nameBase = pathParts.join('_').replace(/[^a-zA-Z0-9_]/g, '_'); return `${method}_${nameBase}`; }