{"id":183,"date":"2026-10-08T00:00:00","date_gmt":"2026-10-07T20:30:00","guid":{"rendered":"https:\/\/www.anynet.ir\/blog\/%d8%a7%d8%b3%d8%aa%d9%81%d8%a7%d8%af%d9%87-%d8%a7%d8%b2-%d9%85%d8%af%d9%84%d9%87%d8%a7%db%8c-%d8%b2%d8%a8%d8%a7%d9%86%db%8c-%d8%a8%d8%b2%d8%b1%da%af-llm-%d8%a8%d8%b1%d8%a7%db%8c-%d8%b3%d8%a7%d8%ae-en\/"},"modified":"2026-10-18T19:07:20","modified_gmt":"2026-10-18T15:37:20","slug":"%d8%a7%d8%b3%d8%aa%d9%81%d8%a7%d8%af%d9%87-%d8%a7%d8%b2-%d9%85%d8%af%d9%84%d9%87%d8%a7%db%8c-%d8%b2%d8%a8%d8%a7%d9%86%db%8c-%d8%a8%d8%b2%d8%b1%da%af-llm-%d8%a8%d8%b1%d8%a7%db%8c-%d8%b3%d8%a7%d8%ae-en","status":"publish","type":"post","link":"https:\/\/www.anynet.ir\/blog\/en\/%d8%a7%d8%b3%d8%aa%d9%81%d8%a7%d8%af%d9%87-%d8%a7%d8%b2-%d9%85%d8%af%d9%84%d9%87%d8%a7%db%8c-%d8%b2%d8%a8%d8%a7%d9%86%db%8c-%d8%a8%d8%b2%d8%b1%da%af-llm-%d8%a8%d8%b1%d8%a7%db%8c-%d8%b3%d8%a7%d8%ae-en\/","title":{"rendered":"Using LLMs to build a Crosswork Planning MCP server"},"content":{"rendered":"<div><\/div>\n<div>\n<div><\/div>\n<\/div>\n<div>\n<div>\n<div>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Introduction<\/h2>\n<p>Crosswork Planning predicts the impact of network changes, traffic growth, new services, and potential failures. Operators usually perform these tasks in the Design application, which provides an interactive graphical interface for simulation, optimization, and analysis.<\/p>\n<p>The same capabilities are available programmatically. The Optimization and Prediction Module (OPM) Python library connects to the DesignAPI service on a Crosswork Planning instance, opens a plan, and runs route simulation, failure impact, and Simulation Analysis on the server. The tutorial <a href=\"https:\/\/xrdocs.io\/automation\/tutorials\/using-llms-for-crosswork-planning-script-prototyping\" style=\"color: rgb(0, 102, 204)\">Using LLMs for Crosswork Planning script prototyping<\/a> showed how an LLM coding agent can be leveraged to write a one-off script against this library.<\/p>\n<p>This tutorial goes one step further. Instead of asking the agent for a script that answers a specific question, we ask it to build a <strong style=\"color: rgb(26, 26, 26)\">Model Context Protocol (MCP) server<\/strong> that exposes Crosswork Planning capabilities as a set of tools that any MCP-capable LLM client can use. Once the server is connected, you may ask questions such as <em>&#8220;What happens if the San Jose to Kansas City link fails?&#8221;<\/em> in natural language, and the LLM decides which simulation to run and explains the results to the operator.<\/p>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">Note:<\/strong> Crosswork Planning is slated to ship with a fully supported MCP server in a future release. This tutorial shows how you can build and use your own MCP server with LLMs, using the Crosswork Planning Python library.<\/p>\n<\/blockquote>\n<p>Building on these capabilities, Crosswork AI offers an Agent Development Kit (ADK) for developing and operating AI agents, enabling MCP-powered network planning applications. An illustrative use case is presented in the Cisco Community blog, <a href=\"https:\/\/community.cisco.com\/t5\/crosswork-automation-hub-blogs\/from-static-to-autonomous-the-evolution-of-network-planning\/ba-p\/5570497\" style=\"color: rgb(0, 102, 204)\">From Static to Autonomous: The Evolution of Network Planning<\/a>. Beyond agent development, the Crosswork AI framework brings together agent observability and tracing, agent evaluation, planning and orchestration services, governed model access, standardized tool integration, and knowledge-graph grounding. Together, these capabilities provide a structured foundation for AI-driven network operations that may not be available through general-purpose LLM interactions alone.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">What you&#8217;ll build<\/h2>\n<p>In this tutorial, you will ask an LLM coding agent to build a single-file Python MCP server for Crosswork Planning, register it in Cursor, and use it to analyze a sample WAN plan.<\/p>\n<p>When the server is connected, a conversation like this:<\/p>\n<pre><code><span>User: I have us_wan.txt in my workspace. Upload it and tell me what happens\n<\/span><span> if the SJC-KCY link fails. Also run simulation analysis on circuits.\n<\/span><\/code><\/pre>\n<p>produces an answer grounded in real simulation output from Crosswork Planning:<\/p>\n<pre><code><span>Agent: 1. upload_plan(us_wan.txt) \u2192 plan_ref upload:abc123, 33 nodes \/ 50 circuits \/ 95 demands\n<\/span><span> 2. failure_sim(plan_ref=upload:abc123, node_a=cr1.sjc, node_b=cr1.kcy)\n<\/span><span> \u2192 10 reroutes; cr2.wdc:to_cr1.nyc oversubscribed at 113.39%\n<\/span><span> 3. get_wc_traffic(plan_ref=upload:abc123, failure_sets=circuits)\n<\/span><span> \u2192 top interface NYC-WDC 113.39% (scenario_count 29 when reported)\n<\/span><span> 4. Summarize findings for the user\n<\/span><\/code><\/pre>\n<p>You don&#8217;t need to write the server yourself. The agent reads the Cisco SDK and writes the code; your job is to set up connectivity, describe the server clearly, and check the results against the Design application.<\/p>\n<p>This takes about 90 minutes, most of it spent on one-time certificate setup.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">How the MCP server works<\/h2>\n<p>Model Context Protocol (MCP) defines how AI clients discover and invoke <strong style=\"color: rgb(26, 26, 26)\">tools<\/strong> (functions with JSON schemas) over a standard transport. An MCP server can also expose <strong style=\"color: rgb(26, 26, 26)\">resources<\/strong> (read-only data, such as a plan summary) and <strong style=\"color: rgb(26, 26, 26)\">prompts<\/strong> (workflow templates the LLM can pull into context). The LLM client decides when to call each tool based on the user&#8217;s question.<\/p>\n<p>The server in this tutorial uses <strong style=\"color: rgb(26, 26, 26)\">stdio<\/strong> transport, which requires it to run on the same Linux workstation as the LLM client (for example, Cursor). Cursor launches the server as a child process and exchanges JSON-RPC messages over stdin and stdout. No network port is opened on your workstation, and there is no separate &#8220;start the server&#8221; step for daily use. The server in turn connects to DesignAPI on the Crosswork Planning instance over mutual TLS (mTLS) on port 30744.<\/p>\n<pre><code><span>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n<\/span><span>\u2502 Linux Workstation (same host as Cursor) \u2502\n<\/span><span>\u2502 \u2502\n<\/span><span>\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 stdin\/stdout \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n<\/span><span>\u2502 \u2502 Cursor \u2502 \u25c4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba \u2502 cp_sample_mcp.py \u2502 \u2502\n<\/span><span>\u2502 \u2502 MCP client \u2502 JSON-RPC stream \u2502 --transport stdio \u2502 \u2502\n<\/span><span>\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502.plan_staging\/ \u2502 \u2502\n<\/span><span>\u2502 \u2502 CARIDEN_HOME=cw-planning\u2502 \u2502\n<\/span><span>\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n<\/span><span>\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/span><span> \u2502 mTLS:30744\n<\/span><span> \u25bc\n<\/span><span> \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n<\/span><span> \u2502 Crosswork Planning VM \u2502\n<\/span><span> \u2502 designapid \u2502\n<\/span><span> \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/span><\/code><\/pre>\n<p align=\"center\"><em>Figure 1: The MCP client spawns the server locally; the server runs simulations on DesignAPI<\/em><\/p>\n<p>The LLM never reads plan files from the Crosswork Planning server. Instead, it reads the plan from your workspace and sends its content to the <code>upload_plan<\/code> tool. The server stages the file locally and returns a <code>plan_id<\/code> and canonical <code>plan_ref<\/code> (<code>upload:&lt;plan_id&gt;<\/code>). Pass that <code>plan_ref<\/code> to every later tool call.<\/p>\n<p>The finished server exposes 14 tools, 5 resources, and 2 prompts:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Category<\/th>\n<th>Tools<\/th>\n<th>What they do<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Plan lifecycle<\/td>\n<td><code>upload_plan<\/code>, <code>list_uploaded_plans<\/code>, <code>delete_uploaded_plan<\/code><\/td>\n<td>Stage a plan from the client and manage staged plans.<\/td>\n<\/tr>\n<tr>\n<td>Catalog<\/td>\n<td><code>get_plan_summary<\/code>, <code>list_circuits<\/code>, <code>list_demands<\/code>, <code>list_lsps<\/code><\/td>\n<td>Read nodes, circuits, demands, and label-switched paths (LSPs).<\/td>\n<\/tr>\n<tr>\n<td>Route simulation<\/td>\n<td><code>get_demand_path<\/code>, <code>get_igp_path<\/code>, <code>get_lsp_path<\/code><\/td>\n<td>Show how a demand, an Interior Gateway Protocol (IGP) shortest path, or an RSVP-TE tunnel or Segment Routing (SR) policy is routed.<\/td>\n<\/tr>\n<tr>\n<td>What-if<\/td>\n<td><code>failure_sim<\/code><\/td>\n<td>Fail a specific circuit and report reroutes and utilization changes.<\/td>\n<\/tr>\n<tr>\n<td>Analysis<\/td>\n<td><code>get_wc_traffic<\/code>, <code>get_traffic_growth<\/code><\/td>\n<td>Run Simulation Analysis for worst-case utilization, and forecast traffic growth.<\/td>\n<\/tr>\n<tr>\n<td>Health<\/td>\n<td><code>designapi_health_check<\/code><\/td>\n<td>Verify certificates, configuration, and DesignAPI reachability.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Compared with other ways of automating Crosswork Planning:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Approach<\/th>\n<th>Strength<\/th>\n<th>Consideration<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Collector external scripts<\/td>\n<td>Integrated into collection chains<\/td>\n<td>Runs only during collection; file-path based<\/td>\n<\/tr>\n<tr>\n<td>Job Manager script jobs<\/td>\n<td>Scheduled, visible in the user interface<\/td>\n<td>Each question needs its own script<\/td>\n<\/tr>\n<tr>\n<td>MCP server (stdio)<\/td>\n<td>Natural-language interface; immediate what-if analysis; no network listener<\/td>\n<td>The MCP client and OPM SDK must run on a host that can reach DesignAPI<\/td>\n<\/tr>\n<tr>\n<td>REST API (CP 8.0)<\/td>\n<td>Platform-agnostic; does not require the SDK<\/td>\n<td>Simulations must run as a Job Manager script or CLI job<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Before you begin<\/h2>\n<p>You need:<\/p>\n<ul>\n<li><strong style=\"color: rgb(26, 26, 26)\">Crosswork Planning 8.0<\/strong> or later, with permission to manage OPM certificates. This tutorial was verified on 2026-10-08 against release 8.0 (Build 385).<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">A Linux workstation<\/strong> with Python 3.11 or later, and TCP reachability to the Crosswork Planning instance on port 30744. The OPM SDK is Linux-oriented; a Linux lab VM or Windows Subsystem for Linux (WSL) is the simplest option.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">The Design\/OPM SDK<\/strong> (<code>cp_design_opm_sdk.tgz<\/code>) from your Crosswork Planning installation or the Cisco software portal.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">An LLM coding agent and MCP client<\/strong>, such as Cursor or Claude Desktop. Other models and clients should work, but their output will differ.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">A sample plan file<\/strong>: this tutorial uses <code>us_wan.txt<\/code> (33 nodes, 50 circuits, 95 demands), exported from the Design application user space.<\/li>\n<\/ul>\n<p>The generation prompts and a reference copy of the server, <code>cp_sample_mcp.py<\/code>, are available on <a href=\"https:\/\/github.com\/flim226\/crosswork\/tree\/main\/sample-scripts\/cp_sample_mcp\" style=\"color: rgb(0, 102, 204)\">GitHub<\/a>.<\/p>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">Keep sensitive data out of your prompts.<\/strong> Don&#8217;t paste passwords, tokens, private keys, customer plan files, or real topology data into an LLM. Use the sample plan while you learn, and keep certificates on disk where only the SDK reads them.<\/p>\n<\/blockquote>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 1: Connect the OPM library to Crosswork Planning<\/h2>\n<p>The MCP server is only as useful as its connection to DesignAPI, so set that up first and confirm it works before involving the agent. This is a one-time task per workstation.<\/p>\n<p><strong style=\"color: rgb(26, 26, 26)\">Start DesignAPI<\/strong> \u2014 obtain a JWT bearer token from Crosswork single sign-on, then check DesignAPI status through the OPM REST API on port 30603 and start it if needed. You may use scripts such as <a href=\"https:\/\/github.com\/flim226\/crosswork\/tree\/main\/sample-scripts\/cw_get_jwt\" style=\"color: rgb(0, 102, 204)\">cw_get_jwt<\/a> to obtain a JWT.<\/p>\n<pre><code><span><span>export<\/span> <span>TOKEN<\/span><span>=<\/span><span>&lt;<\/span>your-jwt-bearer-token<span>&gt;<\/span>\n<\/span><span><span>export<\/span> <span>OPM<\/span><span>=<\/span><span>\"https:\/\/&lt;cp-host&gt;:30603\/cp\/opm-service\/api\/v1\"<\/span>\n<\/span><span><span>curl<\/span> <span>-sk<\/span> <span>-H<\/span> <span>\"Authorization: Bearer <span>$TOKEN<\/span>\"<\/span> <span>\"<span>$OPM<\/span>\/status\"<\/span> <span>|<\/span> python3 <span>-m<\/span> json.tool\n<\/span><span><span>curl<\/span> <span>-sk<\/span> <span>-X<\/span> POST <span>-H<\/span> <span>\"Authorization: Bearer <span>$TOKEN<\/span>\"<\/span> <span>\"<span>$OPM<\/span>\/start\"<\/span>\n<\/span><\/code><\/pre>\n<p>The JWT is used only to onboard certificates. All simulation traffic uses mTLS client certificates.<\/p>\n<p><strong style=\"color: rgb(26, 26, 26)\">Extract the SDK<\/strong> \u2014 the SDK directory becomes <code>CARIDEN_HOME<\/code>:<\/p>\n<pre><code><span><span>mkdir<\/span> <span>-p<\/span> ~\/crosswork-mcp <span>&amp;&amp;<\/span> <span>cd<\/span> ~\/crosswork-mcp\n<\/span><span><span>tar<\/span> xzf cp_design_opm_sdk.tgz\n<\/span><span><span>export<\/span> <span>CARIDEN_HOME<\/span><span>=<\/span><span>$PWD<\/span>\/cw-planning\n<\/span><span><span>ls<\/span> <span>\"<span>$CARIDEN_HOME<\/span>\/lib\/python\/com\/cisco\/wae\/opm\/network\"<\/span>\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">Create a CA-signed client certificate<\/strong> \u2014 Crosswork Planning 8.0 accepts only CA-signed client certificates during onboarding. The SDK&#8217;s <code>generate_client_certs<\/code> script produces self-signed certificates, which onboarding rejects with HTTP 400 or 422. In production, request a client authentication certificate from your enterprise PKI. In a lab, create a private CA with OpenSSL. First create <code>openssl-ca.cnf<\/code>:<\/p>\n<pre><code><span><span><span>[<\/span> <span>req<\/span> <span>]<\/span><\/span>\n<\/span><span><span>default_bits<\/span> <span>=<\/span> <span>4096<\/span>\n<\/span><span><span>prompt<\/span> <span>=<\/span> <span>no<\/span>\n<\/span><span><span>default_md<\/span> <span>=<\/span> <span>sha256<\/span>\n<\/span><span><span>distinguished_name<\/span> <span>=<\/span> <span>dn<\/span>\n<\/span><span><span>x509_extensions<\/span> <span>=<\/span> <span>v3_ca<\/span>\n<\/span><span><span><span>[<\/span> <span>dn<\/span> <span>]<\/span><\/span>\n<\/span><span><span>CN<\/span> <span>=<\/span> <span>Lab Test CA<\/span>\n<\/span><span><span>O<\/span> <span>=<\/span> <span>Lab<\/span>\n<\/span><span><span>C<\/span> <span>=<\/span> <span>US<\/span>\n<\/span><span><span><span>[<\/span> <span>v3_ca<\/span> <span>]<\/span><\/span>\n<\/span><span><span>basicConstraints<\/span> <span>=<\/span> <span>critical, CA:TRUE<\/span>\n<\/span><span><span>keyUsage<\/span> <span>=<\/span> <span>critical, keyCertSign, cRLSign<\/span>\n<\/span><span><span>subjectKeyIdentifier<\/span> <span>=<\/span> <span>hash<\/span>\n<\/span><span><span>authorityKeyIdentifier<\/span> <span>=<\/span> <span>keyid:always,issuer<\/span>\n<\/span><\/code><\/pre>\n<p>Then create <code>openssl-client.cnf<\/code>:<\/p>\n<pre><code><span><span><span>[<\/span> <span>req<\/span> <span>]<\/span><\/span>\n<\/span><span><span>default_bits<\/span> <span>=<\/span> <span>2048<\/span>\n<\/span><span><span>prompt<\/span> <span>=<\/span> <span>no<\/span>\n<\/span><span><span>default_md<\/span> <span>=<\/span> <span>sha256<\/span>\n<\/span><span><span>distinguished_name<\/span> <span>=<\/span> <span>dn<\/span>\n<\/span><span><span>req_extensions<\/span> <span>=<\/span> <span>v3_client<\/span>\n<\/span><span><span><span>[<\/span> <span>dn<\/span> <span>]<\/span><\/span>\n<\/span><span><span>CN<\/span> <span>=<\/span> <span>lab-design-client<\/span>\n<\/span><span><span>O<\/span> <span>=<\/span> <span>Lab<\/span>\n<\/span><span><span>C<\/span> <span>=<\/span> <span>US<\/span>\n<\/span><span><span><span>[<\/span> <span>v3_client<\/span> <span>]<\/span><\/span>\n<\/span><span><span>basicConstraints<\/span> <span>=<\/span> <span>critical, CA:FALSE<\/span>\n<\/span><span><span>keyUsage<\/span> <span>=<\/span> <span>critical, digitalSignature, keyEncipherment<\/span>\n<\/span><span><span>extendedKeyUsage<\/span> <span>=<\/span> <span>clientAuth<\/span>\n<\/span><span><span>subjectKeyIdentifier<\/span> <span>=<\/span> <span>hash<\/span>\n<\/span><\/code><\/pre>\n<p>Generate the CA and the client certificate, then verify them:<\/p>\n<pre><code><span><span>mkdir<\/span> <span>-p<\/span> opm-certs <span>&amp;&amp;<\/span> <span>cd<\/span> opm-certs\n<\/span><span>openssl genrsa <span>-out<\/span> ca_key.pem <span>4096<\/span>\n<\/span><span>openssl req <span>-x509<\/span> <span>-new<\/span> <span>-nodes<\/span> <span>-key<\/span> ca_key.pem <span>-days<\/span> <span>3650<\/span> <span>\\<\/span>\n<\/span><span> <span>-out<\/span> test_ca.pem <span>-config<\/span> <span>..<\/span>\/openssl-ca.cnf\n<\/span><span>openssl genrsa <span>-out<\/span> client_key.pem <span>2048<\/span>\n<\/span><span>openssl req <span>-new<\/span> <span>-key<\/span> client_key.pem <span>-out<\/span> client.csr <span>-config<\/span> <span>..<\/span>\/openssl-client.cnf\n<\/span><span>openssl x509 <span>-req<\/span> <span>-in<\/span> client.csr <span>-CA<\/span> test_ca.pem <span>-CAkey<\/span> ca_key.pem <span>-CAcreateserial<\/span> <span>\\<\/span>\n<\/span><span> <span>-out<\/span> client_cert.pem <span>-days<\/span> <span>825<\/span> <span>-sha256<\/span> <span>-extensions<\/span> v3_client <span>-extfile<\/span> <span>..<\/span>\/openssl-client.cnf\n<\/span><span>openssl x509 <span>-in<\/span> test_ca.pem <span>-noout<\/span> <span>-ext<\/span> basicConstraints <span># CA:TRUE<\/span>\n<\/span><span>openssl x509 <span>-in<\/span> client_cert.pem <span>-noout<\/span> <span>-issuer<\/span> <span>-subject<\/span> <span># issuer differs from subject<\/span>\n<\/span><span>openssl verify <span>-CAfile<\/span> test_ca.pem client_cert.pem <span># OK<\/span>\n<\/span><span><span>cd<\/span> <span>..<\/span>\n<\/span><\/code><\/pre>\n<p>Before using any certificate, inspect it with <code>openssl x509 -text -noout -in &lt;certificate_file&gt;<\/code> and confirm that it is within its validity dates, uses an RSA key of at least 2048 bits (or P-256 or stronger), and is signed with SHA-256 or stronger. A private lab CA is self-signed by design; use it only in a lab.<\/p>\n<p>If you have already put the SDK on <code>LD_LIBRARY_PATH<\/code>, call OpenSSL with a clean library path. The libraries under <code>cw-planning\/lib<\/code> make the system <code>openssl<\/code> CLI fail (<code>undefined symbol: EVP_sm3<\/code>):<\/p>\n<pre><code><span><span>env<\/span> <span>-u<\/span> LD_LIBRARY_PATH openssl x509 <span>-text<\/span> <span>-noout<\/span> <span>-in<\/span> <span>&lt;<\/span>certificate_file<span>&gt;<\/span>\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">Onboard the certificate<\/strong> \u2014 upload the client certificate and its issuing CA through the OPM REST API. Expect HTTP 200 with <code>{\"message\": \"Certificates imported successfully\"}<\/code>:<\/p>\n<pre><code><span><span>curl<\/span> <span>-sk<\/span> <span>-X<\/span> POST <span>\"<span>$OPM<\/span>\/certs\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-H<\/span> <span>\"Authorization: Bearer <span>$TOKEN<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-F<\/span> <span>\"cert=@opm-certs\/client_cert.pem\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-F<\/span> <span>\"ca_cert=@opm-certs\/test_ca.pem\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-F<\/span> <span>\"title=dev-vm-cert\"<\/span>\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">Install the certificates for the SDK<\/strong> \u2014 the SDK loads mTLS settings from fixed file names under <code>$CARIDEN_HOME\/etc\/certs\/<\/code>. Copy the files there, append the DesignAPI server certificate to the trust bundle, and restart DesignAPI:<\/p>\n<pre><code><span><span>cp<\/span> opm-certs\/client_cert.pem <span>$CARIDEN_HOME<\/span>\/etc\/certs\/designapi_user_cert.pem\n<\/span><span><span>cp<\/span> opm-certs\/client_key.pem <span>$CARIDEN_HOME<\/span>\/etc\/certs\/designapi_user_key.pem\n<\/span><span><span>cp<\/span> opm-certs\/test_ca.pem <span>$CARIDEN_HOME<\/span>\/etc\/certs\/ca_cert.pem\n<\/span><span><span>chmod<\/span> <span>600<\/span> <span>$CARIDEN_HOME<\/span>\/etc\/certs\/designapi_user_key.pem\n<\/span><span><span>curl<\/span> <span>-sk<\/span> <span>-H<\/span> <span>\"Authorization: Bearer <span>$TOKEN<\/span>\"<\/span> <span>\"<span>$OPM<\/span>\/certs\/server\"<\/span> <span>&gt;&gt;<\/span> <span>\"<span>$CARIDEN_HOME<\/span>\/etc\/certs\/ca_cert.pem\"<\/span>\n<\/span><span><span>curl<\/span> <span>-sk<\/span> <span>-X<\/span> POST <span>-H<\/span> <span>\"Authorization: Bearer <span>$TOKEN<\/span>\"<\/span> <span>\"<span>$OPM<\/span>\/stop\"<\/span>\n<\/span><span><span>curl<\/span> <span>-sk<\/span> <span>-X<\/span> POST <span>-H<\/span> <span>\"Authorization: Bearer <span>$TOKEN<\/span>\"<\/span> <span>\"<span>$OPM<\/span>\/start\"<\/span>\n<\/span><\/code><\/pre>\n<p>The private key never leaves your workstation; only the public certificate is uploaded.<\/p>\n<p><strong style=\"color: rgb(26, 26, 26)\">Install the MCP dependencies<\/strong> \u2014 the server needs FastMCP and Pydantic:<\/p>\n<pre><code><span>pip <span>install<\/span> <span>\"fastmcp&gt;=3.0.0\"<\/span> <span>\"pydantic&gt;=2.0,&lt;3\"<\/span>\n<\/span><span><span># This tutorial was checked with FastMCP 3.4.5 and Pydantic 2.13.4.<\/span>\n<\/span><\/code><\/pre>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 2: Ask the agent to build the MCP server<\/h2>\n<p>Now describe the server you want. As in the script prototyping tutorial, a good prompt reads like a requirements document. For an MCP server, it also has to carry facts the agent cannot reliably discover on its own, because some SDK docstrings don&#8217;t match the runtime behavior.<\/p>\n<p>Open your agent in the <code>~\/crosswork-mcp<\/code> folder, where it can read the SDK under <code>cw-planning\/<\/code>, and paste the minimal prompt from <a href=\"https:\/\/xrdocs.io\/automation\/tutorials\/using-llms-for-crosswork-planning-mcp-server#appendix-a-minimal-build-prompt\" style=\"color: rgb(0, 102, 204)\">Appendix A<\/a>. It begins like this:<\/p>\n<pre><code><span>You are a Python engineer familiar with FastMCP and Cisco Crosswork Planning.\n<\/span><span>Build a minimal, usable MCP server in one file. Keep the implementation small\n<\/span><span>and direct. Do not add features that are not listed here.\n<\/span><span>## Deliverable\n<\/span><span>Write one executable file, `cp_sample_mcp.py`, for Python 3.11 or newer.\n<\/span><span>...\n<\/span><span>Before writing Cisco calls, inspect the installed SDK under `CARIDEN_HOME` and\n<\/span><span>its local documentation. Use only APIs you can verify there...\n<\/span><span>If a required API is absent, do not invent a substitute and do not write the file.\n<\/span><span>Return `status: blocked` with the paths and symbols you checked.\n<\/span><\/code><\/pre>\n<p>The prompt is long, but each section has a job. Here is what the main parts mean in plain English:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Section of the prompt<\/th>\n<th>What it means<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Inspect the installed SDK under <code>CARIDEN_HOME<\/code><\/td>\n<td>The agent reads the real Cisco library on disk instead of guessing from memory.<\/td>\n<\/tr>\n<tr>\n<td>Return <code>status: blocked<\/code> if a required API is absent<\/td>\n<td>The agent stops and tells you, rather than inventing a function that doesn&#8217;t exist.<\/td>\n<\/tr>\n<tr>\n<td>Runtime: re-exec with <code>os.execv<\/code><\/td>\n<td>Native libraries are only loaded when the process starts, so the server restarts itself once with the right library path. You can then run it with plain <code>python3<\/code>.<\/td>\n<\/tr>\n<tr>\n<td>IceSSL loads mTLS from <code>CARIDEN_HOME\/etc\/certs\/<\/code><\/td>\n<td>Certificates are read from the files you installed in Step 1, never from the source code or command line.<\/td>\n<\/tr>\n<tr>\n<td>HTTP must refuse to start without <code>MCP_API_TOKEN<\/code><\/td>\n<td>The optional remote mode can&#8217;t be started without authentication.<\/td>\n<\/tr>\n<tr>\n<td>Uploaded plans: list, get, and delete only the current owner&#8217;s plans<\/td>\n<td>One client can&#8217;t see or delete another client&#8217;s plans.<\/td>\n<\/tr>\n<tr>\n<td>Verified OPM\/Design operational contract<\/td>\n<td>A short list of exact SDK calls that are known to work on this release, including the cases where a docstring is misleading.<\/td>\n<\/tr>\n<tr>\n<td>MCP surface: resources, prompts, tools<\/td>\n<td>The full list of what the server must expose, with arguments and return values.<\/td>\n<\/tr>\n<tr>\n<td>Finish: run live DesignAPI checks<\/td>\n<td>If <code>us_wan.txt<\/code> is available, the agent must run the tools against the real instance and fix failures before it reports success.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The <strong style=\"color: rgb(26, 26, 26)\">operational contract<\/strong> is the most important part. These are lessons from real runs, written down so the agent doesn&#8217;t repeat them:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Contract item<\/th>\n<th>Why it is in the prompt<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>open_plan(path, host=..., port=..., protocol=\"ssl\")<\/code><\/td>\n<td>The <code>open_plan<\/code> docstring lists <code>http<\/code> and <code>https<\/code>, but the underlying connection manager accepts <code>ssl<\/code> or <code>tcp<\/code>.<\/td>\n<\/tr>\n<tr>\n<td>Reset with <code>model.route_simulation = []<\/code>, never <code>None<\/code><\/td>\n<td>Assigning <code>None<\/code> leaves the failure list in a state where later failure calls raise <code>AttributeError<\/code>.<\/td>\n<\/tr>\n<tr>\n<td>Apply failures with <code>model.route_simulation = [circuit,...]<\/code><\/td>\n<td>Setting <code>circuit.failed = True<\/code> is not the supported what-if mechanism.<\/td>\n<\/tr>\n<tr>\n<td>Pass <code>Node<\/code> objects to <code>shortest_path<\/code>, not name strings<\/td>\n<td>Passing a string raises an Ice <code>ValueError<\/code> and can affect later calls on the same connection.<\/td>\n<\/tr>\n<tr>\n<td><code>route.interfaces<\/code> is unordered<\/td>\n<td>To show hop-by-hop paths, the server walks the topology from the source node instead of trusting set order.<\/td>\n<\/tr>\n<tr>\n<td>Index Simulation Analysis results by model interface<\/td>\n<td><code>sa.interfaces[iface].worst_case_utilization<\/code> returns the vendor&#8217;s worst-case figure for each interface.<\/td>\n<\/tr>\n<tr>\n<td><code>create_growth_plans<\/code> options are kebab-case<\/td>\n<td>GenericTool options such as <code>\"num-periods\"<\/code> are strings in the native library, not Python names, so the agent can&#8217;t discover them by reading Python code.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The agent writes the server to your folder. Expect around 1,000 to 2,000 lines. You don&#8217;t need to read all of it, but skim the tool docstrings: they are what the LLM client reads when deciding which tool to call.<\/p>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">Note:<\/strong> A reference implementation, <code>cp_sample_mcp.py<\/code>, is available on <a href=\"https:\/\/github.com\/flim226\/crosswork\/tree\/main\/sample-scripts\/cp_sample_mcp\" style=\"color: rgb(0, 102, 204)\">GitHub<\/a>. Appendix A also produces <code>cp_sample_mcp.py<\/code>; the steps below call out where generated copies may differ from the reference.<\/p>\n<\/blockquote>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 3: Review how the server is put together<\/h2>\n<p>Whatever the agent generates, the server should follow the same shape as the reference implementation. Checking for these patterns is a quick way to review generated code without reading every line.<\/p>\n<p><strong style=\"color: rgb(26, 26, 26)\">Bootstrap before importing Cisco modules<\/strong> \u2014 the OPM library must be on the Python and native library paths before any <code>com.cisco.wae<\/code> import, so the server sets up the environment first:<\/p>\n<pre><code><span><span>def<\/span> <span>_bootstrap_opm_env<\/span><span>(<\/span><span>)<\/span> <span>-<\/span><span>&gt;<\/span> Path<span>:<\/span>\n<\/span><span> cariden <span>=<\/span> _cariden_home<span>(<\/span><span>)<\/span>\n<\/span><span> <span>if<\/span> <span>not<\/span> <span>(<\/span>cariden <span>\/<\/span> <span>\"lib\"<\/span> <span>\/<\/span> <span>\"python\"<\/span><span>)<\/span><span>.<\/span>is_dir<span>(<\/span><span>)<\/span><span>:<\/span>\n<\/span><span> <span>raise<\/span> RuntimeError<span>(<\/span>\n<\/span><span> <span><span>f\"CARIDEN_HOME not found or invalid: <\/span><span><span>{<\/span>cariden<span>}<\/span><\/span><span>. \"<\/span><\/span>\n<\/span><span> <span>\"Set CARIDEN_HOME or place cw-planning\/ next to this script.\"<\/span>\n<\/span><span> <span>)<\/span>\n<\/span><span> os<span>.<\/span>environ<span>[<\/span><span>\"CARIDEN_HOME\"<\/span><span>]<\/span> <span>=<\/span> <span>str<\/span><span>(<\/span>cariden<span>)<\/span>\n<\/span><span> py_lib <span>=<\/span> <span>str<\/span><span>(<\/span>cariden <span>\/<\/span> <span>\"lib\"<\/span> <span>\/<\/span> <span>\"python\"<\/span><span>)<\/span>\n<\/span><span> <span>if<\/span> py_lib <span>not<\/span> <span>in<\/span> sys<span>.<\/span>path<span>:<\/span>\n<\/span><span> sys<span>.<\/span>path<span>.<\/span>insert<span>(<\/span><span>0<\/span><span>,<\/span> py_lib<span>)<\/span>\n<\/span><span> <span>#... PYTHONPATH and LD_LIBRARY_PATH updated here...<\/span>\n<\/span><span> <span>return<\/span> cariden\n<\/span><span>_apply_env_overrides<span>(<\/span><span>)<\/span>\n<\/span><span>CARIDEN_HOME_PATH <span>=<\/span> _bootstrap_opm_env<span>(<\/span><span>)<\/span>\n<\/span><span><span>from<\/span> com<span>.<\/span>cisco<span>.<\/span>wae<span>.<\/span>opm<span>.<\/span>network <span>import<\/span> open_plan <span># noqa: E402<\/span>\n<\/span><span><span>from<\/span> com<span>.<\/span>cisco<span>.<\/span>wae<span>.<\/span>opm<span>.<\/span>network<span>.<\/span>tools<span>.<\/span>simulation_analysis <span>import<\/span> SimulationAnalysis <span># noqa: E402<\/span>\n<\/span><span><span>from<\/span> fastmcp <span>import<\/span> FastMCP <span># noqa: E402<\/span>\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">Open one plan per call, under a lock<\/strong> \u2014 the OPM objects are not thread-safe, so every tool opens a fresh copy of the plan inside a global lock. The staged upload is never modified:<\/p>\n<pre><code><span><span>@contextmanager<\/span>\n<\/span><span><span>def<\/span> <span>_open_model<\/span><span>(<\/span>plan_path<span>:<\/span> Path<span>)<\/span> <span>-<\/span><span>&gt;<\/span> Iterator<span>[<\/span>Any<span>]<\/span><span>:<\/span>\n<\/span><span> host<span>,<\/span> port<span>,<\/span> protocol <span>=<\/span> _cw_connection<span>(<\/span><span>)<\/span>\n<\/span><span> <span>with<\/span> _opm_lock<span>:<\/span>\n<\/span><span> <span>with<\/span> open_plan<span>(<\/span><span>str<\/span><span>(<\/span>plan_path<span>)<\/span><span>,<\/span> host<span>,<\/span> port<span>,<\/span> protocol<span>)<\/span> <span>as<\/span> network<span>:<\/span>\n<\/span><span> <span>yield<\/span> network<span>.<\/span>model\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">Return structured results<\/strong> \u2014 successful calls return <code>{\"ok\": true,...}<\/code>, and failures raise FastMCP&#8217;s <code>ToolError<\/code> with a short message and hint. An LLM parses structured JSON far more reliably than a stack trace.<\/p>\n<p><strong style=\"color: rgb(26, 26, 26)\">Tell the LLM how to choose tools<\/strong> \u2014 the server&#8217;s instructions steer tool selection. For example, a worst-case question must go to Simulation Analysis, not to repeated failure simulations:<\/p>\n<pre><code><span>FastMCP<span>(<\/span>\n<\/span><span> <span>\"cw-planning-tutorial\"<\/span><span>,<\/span> <span># keep this unique in the MCP client<\/span>\n<\/span><span> instructions<span>=<\/span><span>(<\/span>\n<\/span><span> <span>\"Always upload_plan first when the user provides a plan file, \"<\/span>\n<\/span><span> <span>\"then pass the returned plan_id as plan_ref to simulation tools. \"<\/span>\n<\/span><span> <span>\"Tool routing: for worst-case traffic or Simulation Analysis \"<\/span>\n<\/span><span> <span>\"(e.g. 'worst case under circuit failures', 'most at-risk links'), \"<\/span>\n<\/span><span> <span>\"call get_wc_traffic only \u2014 do not call failure_sim. \"<\/span>\n<\/span><span> <span>#...<\/span>\n<\/span><span> <span>)<\/span><span>,<\/span>\n<\/span><span> auth<span>=<\/span>_build_auth_provider<span>(<\/span><span>)<\/span><span>,<\/span>\n<\/span><span> middleware<span>=<\/span>_build_middleware<span>(<\/span><span>)<\/span><span>,<\/span>\n<\/span><span> strict_input_validation<span>=<\/span><span>True<\/span><span>,<\/span>\n<\/span><span><span>)<\/span>\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">Compare a baseline with a failure<\/strong> \u2014 the <code>failure_sim<\/code> tool runs the simulation twice in the same opened plan, once with no failures and once with the requested circuits failed, and then compares demand paths and interface traffic:<\/p>\n<pre><code><span>model<span>.<\/span>route_simulation <span>=<\/span> <span>[<\/span><span>]<\/span>\n<\/span><span>model<span>.<\/span>traffic_simulation <span>=<\/span> <span>None<\/span>\n<\/span><span>model<span>.<\/span>route_simulation<span>.<\/span>recompute<span>(<\/span><span>)<\/span>\n<\/span><span>before_paths <span>=<\/span> <span>{<\/span><span>str<\/span><span>(<\/span>d<span>)<\/span><span>:<\/span> _path_of<span>(<\/span>d<span>)<\/span> <span>for<\/span> d <span>in<\/span> model<span>.<\/span>demands <span>if<\/span> d<span>.<\/span>active<span>}<\/span>\n<\/span><span>before_ifaces <span>=<\/span> _iface_snapshot<span>(<\/span>model<span>)<\/span>\n<\/span><span>model<span>.<\/span>route_simulation <span>=<\/span> failed\n<\/span><span>model<span>.<\/span>traffic_simulation <span>=<\/span> <span>None<\/span>\n<\/span><span>model<span>.<\/span>route_simulation<span>.<\/span>recompute<span>(<\/span><span>)<\/span>\n<\/span><span>after_ifaces <span>=<\/span> _iface_snapshot<span>(<\/span>model<span>)<\/span>\n<\/span><span>rerouted<span>,<\/span> unrouted <span>=<\/span> _collect_link_failure_reroutes<span>(<\/span>model<span>,<\/span> before_paths<span>)<\/span>\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">Use the Cisco engine for worst-case analysis<\/strong> \u2014 <code>get_wc_traffic<\/code> calls <code>SimulationAnalysis<\/code> and reads the vendor&#8217;s worst-case figure for each interface. It never approximates worst case by looping over <code>failure_sim<\/code>:<\/p>\n<pre><code><span>sa <span>=<\/span> SimulationAnalysis<span>(<\/span>\n<\/span><span> model<span>,<\/span>\n<\/span><span> failure_types<span>=<\/span><span>list<\/span><span>(<\/span>args<span>.<\/span>failure_sets<span>)<\/span><span>,<\/span>\n<\/span><span> max_fail_per_int<span>=<\/span>args<span>.<\/span>max_fail_per_int<span>,<\/span>\n<\/span><span><span>)<\/span>\n<\/span><span>sa<span>.<\/span>cache_valid <span>=<\/span> <span>True<\/span>\n<\/span><span><span>for<\/span> iface <span>in<\/span> model<span>.<\/span>interfaces<span>:<\/span>\n<\/span><span> util <span>=<\/span> sa<span>.<\/span>interfaces<span>[<\/span>iface<span>]<\/span><span>.<\/span>worst_case_utilization\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">Keep stdout clean<\/strong> \u2014 in stdio mode, stdout carries the MCP protocol. All startup messages and audit logs go to stderr. A single stray <code>print()<\/code> to stdout breaks the connection with the client.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 4: Test the server without an MCP client<\/h2>\n<p>Before connecting Cursor, call the tools directly from Python. This separates DesignAPI problems from MCP client problems.<\/p>\n<p>Place <code>us_wan.txt<\/code> in <code>~\/crosswork-mcp<\/code>, then run the following. Generated servers from the appendices read <code>CW_DESIGNAPI_HOST<\/code> and <code>CW_DESIGNAPI_PORT<\/code>. Some GitHub reference copies also accept <code>CW_HOST<\/code> \/ <code>--cw-host<\/code>; set both if you are unsure which file you are running.<\/p>\n<p>Always pass the canonical <code>plan_ref<\/code> (<code>upload:&lt;plan_id&gt;<\/code>). A bare <code>plan_id<\/code> works on some reference copies, but a server generated from the minimal prompt treats an unmatched string as a filesystem path and fails with <code>plan file not found<\/code>.<\/p>\n<pre><code><span><span>cd<\/span> ~\/crosswork-mcp\n<\/span><span><span>export<\/span> <span>CARIDEN_HOME<\/span><span>=<\/span><span>$PWD<\/span>\/cw-planning\n<\/span><span><span>export<\/span> <span>CW_DESIGNAPI_HOST<\/span><span>=<\/span><span>&lt;<\/span>cp-host<span>&gt;<\/span>\n<\/span><span><span>export<\/span> <span>CW_DESIGNAPI_PORT<\/span><span>=<\/span><span>30744<\/span>\n<\/span><span><span>export<\/span> <span>CW_DESIGNAPI_PROTOCOL<\/span><span>=<\/span>ssl\n<\/span><span><span># Optional alias used by some reference copies:<\/span>\n<\/span><span><span>export<\/span> <span>CW_HOST<\/span><span>=<\/span><span>&lt;<\/span>cp-host<span>&gt;<\/span>\n<\/span><span>python3 <span>-c<\/span> <span>\"\n<\/span><\/span><span><span>from pathlib import Path\n<\/span><\/span><span><span>import cp_sample_mcp as m # Appendix A\/B deliverable or GitHub reference\n<\/span><\/span><span><span>plan = Path('us_wan.txt').read_text()\n<\/span><\/span><span><span>up = m.upload_plan('us_wan.txt', plan)\n<\/span><\/span><span><span>plan_ref = up.get('plan_ref') or ('upload:' + up['plan_id'])\n<\/span><\/span><span><span>print('upload nodes\/circuits\/demands:',\n<\/span><\/span><span><span> up.get('nodes') or up.get('node_count'),\n<\/span><\/span><span><span> up.get('circuits') or up.get('circuit_count'),\n<\/span><\/span><span><span> up.get('demands') or up.get('demand_count'))\n<\/span><\/span><span><span>print('health:', m.designapi_health_check(plan_ref=plan_ref))\n<\/span><\/span><span><span>print('failure:', m.failure_sim(\n<\/span><\/span><span><span> plan_ref=plan_ref, node_a='cr1.sjc', node_b='cr1.kcy'))\n<\/span><\/span><span><span>\"<\/span>\n<\/span><\/code><\/pre>\n<p>If <code>upload_plan<\/code> is not a module-level function (the full prompt may nest tools inside a <code>register_all<\/code> helper), call the same operations through an in-process FastMCP <code>Client<\/code> against the server object the file constructs.<\/p>\n<p>Check that:<\/p>\n<ul>\n<li><code>upload<\/code> reports <strong style=\"color: rgb(26, 26, 26)\">33<\/strong> nodes, <strong style=\"color: rgb(26, 26, 26)\">50<\/strong> circuits, and <strong style=\"color: rgb(26, 26, 26)\">95<\/strong> demands (field names may be <code>nodes<\/code> or <code>node_count<\/code>, and so on).<\/li>\n<li><code>health<\/code> is <code>ok: true<\/code> and DesignAPI is reachable. Generated servers nest this under <code>reachability.reachable<\/code> and <code>certificates<\/code>; some reference copies use top-level <code>certs_ok<\/code> and <code>design_api_reachable<\/code>.<\/li>\n<li><code>failure<\/code> reports <code>rerouted_count: 10<\/code>.<\/li>\n<\/ul>\n<p>You may see IceSSL deprecation warnings and a urllib3 CiscoSSL notice. They don&#8217;t affect simulation results. A lab IceSSL private key that is group-readable (<code>0664<\/code>) should produce a warning, not a startup failure.<\/p>\n<p>Next, confirm that the server starts in stdio mode. Use <code>cp_sample_mcp.py<\/code>, matching the file you generated:<\/p>\n<pre><code><span>python3 cp_sample_mcp.py <span>--transport<\/span> stdio\n<\/span><\/code><\/pre>\n<p>Stdout must stay empty: it carries the MCP protocol. Generated servers typically log one line to stderr (<code>CARIDEN_HOME=... DesignAPI=&lt;cp-host&gt;:30744\/ssl<\/code>) and then wait. The GitHub reference prints a short banner such as:<\/p>\n<pre><code><span>Crosswork Planning MCP Server\n<\/span><span> CARIDEN_HOME: \/home\/user\/crosswork-mcp\/cw-planning\n<\/span><span> DesignAPI: &lt;cp-host&gt;:30744\n<\/span><span> Staging: \/home\/user\/crosswork-mcp\/.plan_staging\n<\/span><span> Transport: stdio\n<\/span><\/code><\/pre>\n<p>The process then waits for input. Press Ctrl+C to exit.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 5: Connect the server to Cursor<\/h2>\n<p>Add the server to your MCP settings, either in <code>.cursor\/mcp.json<\/code> in your project or under <strong style=\"color: rgb(26, 26, 26)\">Settings \u2192 MCP<\/strong>. Use absolute paths, and replace <code>\/home\/user\/crosswork-mcp<\/code> with your folder.<\/p>\n<p>Give the server a <strong style=\"color: rgb(26, 26, 26)\">unique <code>mcpServers<\/code> key<\/strong>. Cursor namespaces tools by that key. If this workstation already has a Crosswork Planning MCP (<code>cp_sample_mcp<\/code>, <code>cp-sample-mcp<\/code>, or similar), do not reuse that key or you will collide with the existing tools. This tutorial uses <code>cw-planning-tutorial<\/code>. Keep the tool names (<code>upload_plan<\/code>, <code>failure_sim<\/code>, and so on) so the agent can follow the workflow below.<\/p>\n<p>Generated servers from Appendix A accept <code>--designapi-host<\/code>. The GitHub reference and some Appendix B copies use <code>--cw-host<\/code>. Point <code>args<\/code> at the file you actually generated (<code>cp_sample_mcp.py<\/code> or <code>cp_sample_mcp.py<\/code>).<\/p>\n<pre><code><span><span>{<\/span>\n<\/span><span> <span>\"mcpServers\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"cw-planning-tutorial\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"command\"<\/span><span>:<\/span> <span>\"python3\"<\/span><span>,<\/span>\n<\/span><span> <span>\"args\"<\/span><span>:<\/span> <span>[<\/span>\n<\/span><span> <span>\"\/home\/user\/crosswork-mcp\/cp_sample_mcp.py\"<\/span><span>,<\/span>\n<\/span><span> <span>\"--transport\"<\/span><span>,<\/span>\n<\/span><span> <span>\"stdio\"<\/span><span>,<\/span>\n<\/span><span> <span>\"--designapi-host\"<\/span><span>,<\/span>\n<\/span><span> <span>\"&lt;cp-host&gt;\"<\/span><span>,<\/span>\n<\/span><span> <span>\"--cariden-home\"<\/span><span>,<\/span>\n<\/span><span> <span>\"\/home\/user\/crosswork-mcp\/cw-planning\"<\/span>\n<\/span><span> <span>]<\/span><span>,<\/span>\n<\/span><span> <span>\"env\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"CARIDEN_HOME\"<\/span><span>:<\/span> <span>\"\/home\/user\/crosswork-mcp\/cw-planning\"<\/span><span>,<\/span>\n<\/span><span> <span>\"CW_DESIGNAPI_HOST\"<\/span><span>:<\/span> <span>\"&lt;cp-host&gt;\"<\/span><span>,<\/span>\n<\/span><span> <span>\"CW_DESIGNAPI_PORT\"<\/span><span>:<\/span> <span>\"30744\"<\/span><span>,<\/span>\n<\/span><span> <span>\"CW_DESIGNAPI_PROTOCOL\"<\/span><span>:<\/span> <span>\"ssl\"<\/span><span>,<\/span>\n<\/span><span> <span>\"CW_HOST\"<\/span><span>:<\/span> <span>\"&lt;cp-host&gt;\"<\/span><span>,<\/span>\n<\/span><span> <span>\"PYTHONPATH\"<\/span><span>:<\/span> <span>\"\/home\/user\/crosswork-mcp\/cw-planning\/lib\/python\"<\/span><span>,<\/span>\n<\/span><span> <span>\"LD_LIBRARY_PATH\"<\/span><span>:<\/span> <span>\"\/home\/user\/crosswork-mcp\/cw-planning\/lib:\/home\/user\/crosswork-mcp\/cw-planning\/lib\/python\"<\/span>\n<\/span><span> <span>}<\/span>\n<\/span><span> <span>}<\/span>\n<\/span><span> <span>}<\/span>\n<\/span><span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p>Reload MCP servers in Cursor. You don&#8217;t start the server yourself: Cursor launches it when it needs a tool. The MCP panel should list <strong style=\"color: rgb(26, 26, 26)\">14 tools<\/strong>, <strong style=\"color: rgb(26, 26, 26)\">2 prompts<\/strong>, and the health\/upload resources. The three <code>plan:\/\/{plan_id}\/...<\/code> resources are URI templates: some clients list all five, while an in-process FastMCP client lists only the two static URIs until a <code>plan_id<\/code> is known.<\/p>\n<p>Claude Desktop uses the same <code>command<\/code>, <code>args<\/code>, and <code>env<\/code> entries in <code>claude_desktop_config.json<\/code>.<\/p>\n<p>To confirm the connection, ask the agent:<\/p>\n<pre><code><span>Run designapi_health_check on the cw-planning-tutorial server.\n<\/span><\/code><\/pre>\n<p>A healthy server returns <code>ok: true<\/code> and a reachable DesignAPI. The exact JSON shape varies. A generated minimal server looks like this:<\/p>\n<pre><code><span><span>{<\/span>\n<\/span><span> <span>\"ok\"<\/span><span>:<\/span> <span>true<\/span><span>,<\/span>\n<\/span><span> <span>\"cariden_home\"<\/span><span>:<\/span> <span>\"\/home\/user\/crosswork-mcp\/cw-planning\"<\/span><span>,<\/span>\n<\/span><span> <span>\"designapi\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"host\"<\/span><span>:<\/span> <span>\"&lt;cp-host&gt;\"<\/span><span>,<\/span>\n<\/span><span> <span>\"port\"<\/span><span>:<\/span> <span>30744<\/span><span>,<\/span>\n<\/span><span> <span>\"protocol\"<\/span><span>:<\/span> <span>\"ssl\"<\/span>\n<\/span><span> <span>}<\/span><span>,<\/span>\n<\/span><span> <span>\"certificates\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"ca_present\"<\/span><span>:<\/span> <span>true<\/span><span>,<\/span>\n<\/span><span> <span>\"client_cert_present\"<\/span><span>:<\/span> <span>true<\/span><span>,<\/span>\n<\/span><span> <span>\"client_key_present\"<\/span><span>:<\/span> <span>true<\/span><span>,<\/span>\n<\/span><span> <span>\"problems\"<\/span><span>:<\/span> <span>[<\/span><span>]<\/span>\n<\/span><span> <span>}<\/span><span>,<\/span>\n<\/span><span> <span>\"reachability\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"reachable\"<\/span><span>:<\/span> <span>true<\/span>\n<\/span><span> <span>}<\/span>\n<\/span><span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p>Some reference copies flatten that into top-level <code>certs_ok<\/code>, <code>design_api_reachable<\/code>, <code>crosswork_host<\/code>, and <code>design_api_port<\/code>. Either shape is fine as long as DesignAPI is reachable and the certificate files are present.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 6: Ask planning questions<\/h2>\n<p>With the server connected, open <code>us_wan.txt<\/code> in your workspace and ask questions in plain English. You don&#8217;t need to name the tools; the LLM picks them from their descriptions. The following prompts were verified against <code>us_wan.txt<\/code>. Your agent&#8217;s wording will differ, but the numbers should match.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Simulate a link failure<\/h3>\n<p>Paste this prompt into your agent:<\/p>\n<pre><code><span>I have us_wan.txt in my workspace. Upload it and tell me what happens if the\n<\/span><span>link between cr1.sjc and cr1.kcy fails.\n<\/span><\/code><\/pre>\n<p>The agent uploads the plan, optionally lists circuits at <code>sjc<\/code> to find the link, and then calls <code>failure_sim<\/code>:<\/p>\n<pre><code><span><span>{<\/span>\n<\/span><span> <span>\"tool\"<\/span><span>:<\/span> <span>\"failure_sim\"<\/span><span>,<\/span>\n<\/span><span> <span>\"arguments\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"plan_ref\"<\/span><span>:<\/span> <span>\"upload:a1b2c3d4e5f6\"<\/span><span>,<\/span>\n<\/span><span> <span>\"node_a\"<\/span><span>:<\/span> <span>\"cr1.sjc\"<\/span><span>,<\/span>\n<\/span><span> <span>\"node_b\"<\/span><span>:<\/span> <span>\"cr1.kcy\"<\/span>\n<\/span><span> <span>}<\/span>\n<\/span><span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p>The response lists the failed circuits, the number of rerouted and unrouted demands, a sample of reroutes with before and after paths, the largest interface traffic changes, and any newly oversubscribed interfaces. Interface identifiers appear as either <code>cr2.wdc:to_cr1.nyc<\/code> or the SDK key <code>if{cr2.wdc|to_cr1.nyc}<\/code> \u2014 both name the same object. The key results, re-verified on 2026-10-08 against DesignAPI on release 8.0.0 (Build 385), are:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Metric<\/th>\n<th>Baseline<\/th>\n<th>After SJC\u2013KCY failure<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Active demands<\/td>\n<td>95<\/td>\n<td>95<\/td>\n<\/tr>\n<tr>\n<td>Rerouted demands<\/td>\n<td>0<\/td>\n<td>10<\/td>\n<\/tr>\n<tr>\n<td>Unrouted demands<\/td>\n<td>0<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td><code>cr1.sjc:to_cr1.kcy<\/code> traffic<\/td>\n<td>978.6 Mbps<\/td>\n<td>0<\/td>\n<\/tr>\n<tr>\n<td><code>cr2.wdc:to_cr1.nyc<\/code> utilization<\/td>\n<td>85.6%<\/td>\n<td>113.39%<\/td>\n<\/tr>\n<tr>\n<td><code>cr1.nyc:to_cr2.wdc<\/code> utilization<\/td>\n<td>\u2014<\/td>\n<td>100.02%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Traffic detours through the northern ring (San Jose, Chicago, New York, Washington DC) and oversubscribes the New York to Washington DC corridor.<\/p>\n<p>To see a single demand&#8217;s normal path, follow up with:<\/p>\n<pre><code><span>Show me the baseline path for the demand from er1.sjc to er1.mia.\n<\/span><\/code><\/pre>\n<p>The agent calls <code>get_demand_path<\/code> and reports 6 hops, a path metric of 2169, and an average latency of <strong style=\"color: rgb(26, 26, 26)\">33.39 ms<\/strong>.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Find the most at-risk links<\/h3>\n<p>Paste this prompt into your agent:<\/p>\n<pre><code><span>Which interfaces are most at risk under any single circuit failure?\n<\/span><\/code><\/pre>\n<p>The agent calls <code>get_wc_traffic<\/code> with <code>failure_sets: \"circuits\"<\/code>, which runs Simulation Analysis on DesignAPI. Expect this to take 10 to 30 seconds. The top interface is <code>cr2.wdc:to_cr1.nyc<\/code> (<code>if{cr2.wdc|to_cr1.nyc}<\/code>) at <strong style=\"color: rgb(26, 26, 26)\">113.39%<\/strong> worst-case utilization \u2014 the same link that the SJC\u2013KCY failure oversubscribed. When the server exposes Cisco&#8217;s scenario list, <code>scenario_count<\/code> is <strong style=\"color: rgb(26, 26, 26)\">29<\/strong> for this plan; generated minimal servers may omit that field and still return the same ranked utilizations.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Forecast traffic growth<\/h3>\n<p>Paste this prompt into your agent:<\/p>\n<pre><code><span>If demand grows 33% over one period, which links become oversubscribed?\n<\/span><\/code><\/pre>\n<p>The agent calls <code>get_traffic_growth<\/code>, which sets a growth rate on every active demand, runs the Create Growth Plans tool on DesignAPI, and compares utilization before and after. The New York to Washington DC circuit reaches <strong style=\"color: rgb(26, 26, 26)\">113.83%<\/strong>.<\/p>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">Note:<\/strong> Behavior of the <code>create_growth_plans<\/code> tool varies by build. On release 8.0.0 (Build 385) it registers a growth plan without updating demand traffic. The server detects this case, applies the compound growth formula ((1 + g\/100)^{periods}) directly to demand traffic, and reports <code>fallback_used: true<\/code> or <code>compound_fallback_applied: true<\/code>. Either flag means the numbers still come from the documented COMPOUND formula, not from a skipped forecast.<\/p>\n<\/blockquote>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Combine questions<\/h3>\n<p>Because the LLM keeps context across the conversation, you can chain analyses into one capacity planning request:<\/p>\n<pre><code><span>Upload us_wan.txt, forecast 33% growth over one period, then run worst-case\n<\/span><span>analysis on circuit failures, and summarize which circuits are oversubscribed\n<\/span><span>or near capacity.\n<\/span><\/code><\/pre>\n<p>The server also provides this sequence as the <code>capacity_planning_workflow<\/code> prompt, and the link failure sequence as <code>link_failure_workflow<\/code>. In Cursor, these appear as reusable prompt templates.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Check the results in Design<\/h3>\n<p>The LLM&#8217;s summary is only as good as the simulation behind it, so confirm at least one result in the Design application:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>MCP tool<\/th>\n<th>Design equivalent<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>get_demand_path<\/code><\/td>\n<td>Demand routing in route simulation<\/td>\n<\/tr>\n<tr>\n<td><code>failure_sim<\/code><\/td>\n<td>Failure simulation of a circuit<\/td>\n<\/tr>\n<tr>\n<td><code>get_wc_traffic<\/code><\/td>\n<td>Simulation Analysis<\/td>\n<\/tr>\n<tr>\n<td><code>get_traffic_growth<\/code><\/td>\n<td>Create Growth Plans<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For the SJC\u2013KCY scenario, open <code>us_wan.txt<\/code> in Design, fail the circuit between <code>cr1.sjc<\/code> and <code>cr1.kcy<\/code>, run route and traffic simulation, and confirm 10 reroutes and New York to Washington DC utilization of about 113%.<\/p>\n<p>You&#8217;ve built and used your first Crosswork Planning MCP server.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 7: Fix a failed tool call with the agent<\/h2>\n<p>Generated servers don&#8217;t always work the first time. When a tool call fails, the fastest fix is usually to give the error back to the agent, together with what you expected.<\/p>\n<p>One failure that the operational contract in the prompt guards against happens when the IGP shortest-path call receives node names instead of node objects:<\/p>\n<pre><code><span>ValueError: invalid value for argument 1 in operation getShortestPathRouteRecord\n<\/span><\/code><\/pre>\n<p>The <code>shortest_path<\/code> function needs OPM <code>Node<\/code> objects. A server that passes the string <code>\"cr1.sjc\"<\/code> gets this error from the Ice runtime, and later calls on the same pooled connection can fail too. A related mistake reports this error as a certificate or connectivity problem, which sends you to check certificates that are actually fine.<\/p>\n<p>To fix a failure like this:<\/p>\n<ol>\n<li>\n<p>Copy the tool&#8217;s error response and any stderr lines around it.<\/p>\n<\/li>\n<li>\n<p>Paste them into the agent with a short request, for example:<\/p>\n<pre><code><span>get_igp_path failed with the error below. Resolve source and destination with\n<\/span><span>model.nodes[name] and pass those Node objects to shortest_path. Report SDK\n<\/span><span>exceptions with their real exception class, not as certificate errors.\n<\/span><span>Keep everything else the same.\n<\/span><span>&lt;paste the error here&gt;\n<\/span><\/code><\/pre>\n<\/li>\n<li>\n<p>Reload the MCP server in Cursor and ask the same question again.<\/p>\n<\/li>\n<\/ol>\n<p>When you find a new failure like this, add it to the operational contract in your prompt. The next time you or a colleague generate the server, the agent avoids the mistake from the start.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 8: Improve the prompt<\/h2>\n<p>The minimal prompt produces a server that works well for one person on one workstation. The full prompt in <a href=\"https:\/\/xrdocs.io\/automation\/tutorials\/using-llms-for-crosswork-planning-mcp-server#appendix-b-full-build-prompt\" style=\"color: rgb(0, 102, 204)\">Appendix B<\/a> produces the reference <code>cp_sample_mcp.py<\/code> design and adds the controls you need before sharing the server:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Addition<\/th>\n<th>Why it helps<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>A prerequisite gate that returns <code>status: blocked<\/code> with evidence<\/td>\n<td>The agent proves every Cisco API it uses exists in the installed SDK before generating any code.<\/td>\n<\/tr>\n<tr>\n<td>A compatibility-adapter section for version-sensitive calls<\/td>\n<td>Release-specific SDK calls are kept in one place, which makes upgrades easier to review.<\/td>\n<\/tr>\n<tr>\n<td>A SQLite plan registry with atomic, owner-scoped uploads<\/td>\n<td>Uploads are never partially visible, and cleanup is safe while a simulation is running.<\/td>\n<\/tr>\n<tr>\n<td>A bearer-token file mapping tokens to owners for HTTP<\/td>\n<td>Each remote client gets its own identity and sees only its own plans.<\/td>\n<\/tr>\n<tr>\n<td>Fail-closed certificate checks<\/td>\n<td>The server refuses to start with expired certificates, weak keys, or SHA-1 signatures, and warns about self-signed certificates.<\/td>\n<\/tr>\n<tr>\n<td>Hard caps on every list, path, and response size<\/td>\n<td>A large plan can&#8217;t produce a response too big for the LLM&#8217;s context window.<\/td>\n<\/tr>\n<tr>\n<td>A classified validation checklist (offline, SDK, live, deployment)<\/td>\n<td>The agent reports which checks it actually ran and marks the rest as not run, rather than as passed.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Try generating the server again with the full prompt, then repeat Steps 4 to 6. The simulation results should be the same.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">What you learned<\/h2>\n<ul>\n<li><strong style=\"color: rgb(26, 26, 26)\">Give the agent the real SDK.<\/strong> Pointing the agent at the installed library and its documentation keeps it grounded in APIs that exist in your release.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Write down what the docs don&#8217;t say.<\/strong> An operational contract of verified calls prevents the agent from following a misleading docstring.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Design tools for the LLM, not just for Python.<\/strong> Clear docstrings, server instructions, and structured <code>{\"ok\": true}<\/code> responses decide whether the LLM picks the right tool and reads the result correctly.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Check every result in Design.<\/strong> The MCP server makes simulation conversational, but Crosswork Planning remains the authority on the numbers.<\/li>\n<\/ul>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Before you use a generated server in production<\/h2>\n<p>A working sample session is a starting point, not proof that the server is safe to share. Before using it beyond your own workstation, make sure that:<\/p>\n<ul>\n<li><input type=\"checkbox\" disabled=\"\"> Someone who reads Python has reviewed the server, especially plan upload and file handling.<\/li>\n<li><input type=\"checkbox\" disabled=\"\"> The server passes a linter such as <code>pylint<\/code> or <code>ruff<\/code>.<\/li>\n<li><input type=\"checkbox\" disabled=\"\"> The source contains no passwords, tokens, private keys, certificates, or deployment-specific addresses. Set the Crosswork host through <code>CW_DESIGNAPI_HOST<\/code> (and <code>CW_HOST<\/code> if your file still reads that alias) or the MCP client <code>env<\/code> block.<\/li>\n<li><input type=\"checkbox\" disabled=\"\"> Every certificate in <code>$CARIDEN_HOME\/etc\/certs\/<\/code> has been checked with <code>openssl x509 -text -noout<\/code>, and the private key is mode <code>0600<\/code>.<\/li>\n<li><input type=\"checkbox\" disabled=\"\"> You&#8217;ve tested bad input: a malformed plan, an oversized upload, a node pair with parallel circuits, and a node name that doesn&#8217;t exist.<\/li>\n<li><input type=\"checkbox\" disabled=\"\"> HTTP mode, if you use it, refuses to start without authentication, binds to <code>127.0.0.1<\/code> by default, and sits behind a TLS reverse proxy when remote access is enabled. The reference implementation authenticates HTTP clients with a single shared <code>MCP_API_TOKEN<\/code>; for a multi-user deployment, use a production token verifier with a token per owner, as described in the full prompt.<\/li>\n<li><input type=\"checkbox\" disabled=\"\"> Uploaded plans are cleaned up, either with <code>delete_uploaded_plan<\/code> or by setting <code>CW_PLAN_TTL_HOURS<\/code>.<\/li>\n<\/ul>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Next steps<\/h2>\n<ul>\n<li><strong style=\"color: rgb(26, 26, 26)\">Extend the server:<\/strong> prove a new OPM capability in a Python session with <code>open_plan<\/code>, add it as a <code>@mcp.tool<\/code> with typed parameters and a clear docstring, test it by direct import as in Step 4, and then reload the server in Cursor.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Share the server:<\/strong> run it with <code>--transport http<\/code> on a shared host behind a TLS reverse proxy so several planners can use one installation. The <a href=\"https:\/\/github.com\/flim226\/crosswork\/tree\/main\/sample-scripts\/cp_sample_mcp\" style=\"color: rgb(0, 102, 204)\">README<\/a> covers remote deployment and a sample systemd unit.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Learn more about the libraries:<\/strong> read the <a href=\"https:\/\/developer.cisco.com\/docs\/crosswork\/planning\/customization-scripts-using-crosswork-planning-designopm-library-package\/\" style=\"color: rgb(0, 102, 204)\">Crosswork Planning Design\/OPM library package<\/a> and <a href=\"https:\/\/developer.cisco.com\/docs\/crosswork\/planning\/authentication\/#authentication-for-crosswork-planning-design-rpc-and-opm-python-library-package\" style=\"color: rgb(0, 102, 204)\">authentication for the Design RPC and OPM Python library<\/a> on Cisco DevNet.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Learn more about MCP:<\/strong> read the <a href=\"https:\/\/modelcontextprotocol.io\" style=\"color: rgb(0, 102, 204)\">Model Context Protocol specification<\/a> and the <a href=\"https:\/\/gofastmcp.com\" style=\"color: rgb(0, 102, 204)\">FastMCP documentation<\/a>.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Script without MCP:<\/strong> for a single repeatable change to a plan, see <a href=\"https:\/\/xrdocs.io\/automation\/tutorials\/using-llms-for-crosswork-planning-script-prototyping\" style=\"color: rgb(0, 102, 204)\">Using LLMs for Crosswork Planning script prototyping<\/a> and <a href=\"https:\/\/xrdocs.io\/automation\/tutorials\/using-external-scripts-to-manipulate-network-models-in-crosswork-planning\" style=\"color: rgb(0, 102, 204)\">Using external scripts to manipulate network models in Crosswork Planning<\/a>.<\/li>\n<\/ul>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Appendix A: Minimal build prompt<\/h2>\n<p>This is the full minimal prompt from Step 2. It is also available as <a href=\"https:\/\/github.com\/flim226\/crosswork\/blob\/main\/sample-scripts\/cp_sample_mcp\/generation_prompt_simple.md\" style=\"color: rgb(0, 102, 204)\"><code>generation_prompt_simple.md<\/code><\/a>.<\/p>\n<details>\n<summary>Show the minimal prompt<\/summary>\n<pre><code><span><span><span>#<\/span> One-shot prompt: minimal Crosswork Planning MCP server<\/span>\n<\/span><span>You are a Python engineer familiar with FastMCP and Cisco Crosswork Planning.\n<\/span><span>Build a minimal, usable MCP server in one file. Keep the implementation small\n<\/span><span>and direct. Do not add features that are not listed here.\n<\/span><span><span><span>##<\/span> Deliverable<\/span>\n<\/span><span>Write one executable file, <span>`cp_sample_mcp.py`<\/span>, for Python 3.11 or newer.\n<\/span><span>Name the FastMCP server <span>`cw-planning-tutorial`<\/span> so it does not collide with\n<\/span><span>other Crosswork MCP servers already registered in the client.\n<\/span><span><span>-<\/span> Start with <span>`#!\/usr\/bin\/env python3`<\/span> and a short module docstring that shows\n<\/span><span> how to run stdio and authenticated HTTP.\n<\/span><span><span>-<\/span> Use <span>`fastmcp.FastMCP`<\/span>, Pydantic v2, and type hints.\n<\/span><span><span>-<\/span> Return JSON-serializable <span>`{\"ok\": True,...}`<\/span> results from every tool.\n<\/span><span><span>-<\/span> Raise FastMCP <span>`ToolError`<\/span> or <span>`ResourceError`<\/span> for expected failures. Include a\n<\/span><span> short hint and do not return stack traces, secrets, or certificate contents.\n<\/span><span><span>-<\/span> Send logs and startup messages to stderr. Leave stdout for MCP stdio traffic.\n<\/span><span><span>-<\/span> Do not emit TODOs, pseudocode, mocks, or extra modules.\n<\/span><span>Before writing Cisco calls, inspect the installed SDK under <span>`CARIDEN_HOME`<\/span> and\n<\/span><span>its local documentation. Use only APIs you can verify there, especially\n<\/span><span><span>`open_plan`<\/span>, route and traffic simulation, LSP and segment-list access,\n<\/span><span><span>`SimulationAnalysis`<\/span>, and the Design RPC <span>`create_growth_plans`<\/span> tool. If a\n<\/span><span>required API is absent, do not invent a substitute and do not write the file.\n<\/span><span>Return <span>`status: blocked`<\/span> with the paths and symbols you checked. If an API\n<\/span><span>exists but DesignAPI is unreachable or unlicensed at runtime, keep the tool and\n<\/span><span>return a clear capability error.\n<\/span><span>The Python docstrings are incomplete and sometimes wrong. Treat the\n<\/span><span><span><span>**<\/span><span>Verified OPM\/Design operational contract<\/span><span>**<\/span><\/span> section below as mandatory\n<\/span><span>ground truth for this release even when it contradicts an <span>`open_plan`<\/span>\n<\/span><span>docstring. GenericTool JSON option names are not Python symbols; they are\n<\/span><span>still required and must use the kebab-case names listed there (also present\n<\/span><span>as strings in <span>`libdesignservice.so`<\/span>). Do not invent camelCase tool options.\n<\/span><span><span><span>##<\/span> Runtime<\/span>\n<\/span><span>Resolve <span>`CARIDEN_HOME`<\/span> from <span>`--cariden-home`<\/span>, then the environment, then a\n<\/span><span>local <span>`cw-planning`<\/span> directory. Require <span>`&lt;CARIDEN_HOME&gt;\/lib\/python`<\/span>, add it to\n<\/span><span><span>`sys.path`<\/span>, and put both <span>`&lt;CARIDEN_HOME&gt;\/lib`<\/span> and <span>`&lt;CARIDEN_HOME&gt;\/lib\/python`<\/span>\n<\/span><span>on <span>`LD_LIBRARY_PATH`<\/span>. Setting <span>`os.environ[\"LD_LIBRARY_PATH\"]`<\/span> in an already\n<\/span><span>running process does <span><span>**<\/span><span>not<\/span><span>**<\/span><\/span> load <span>`libIceDiscovery.so`<\/span>. After updating native\n<\/span><span>paths, if <span>`import Ice`<\/span> \/ <span>`IcePy`<\/span> would fail, re-exec the same interpreter\n<\/span><span>with <span>`os.execv`<\/span> (preserving argv and the new env) <span><span>**<\/span><span>before<\/span><span>**<\/span><\/span> any Cisco\n<\/span><span>import. Fail startup with an actionable message when <span>`lib\/python`<\/span> is missing.\n<\/span><span>Read configuration from the environment:\n<\/span><span><span>-<\/span> <span>`CW_DESIGNAPI_HOST`<\/span>, <span>`CW_DESIGNAPI_PORT`<\/span> (default <span>`30744`<\/span>),\n<\/span><span> <span>`CW_DESIGNAPI_PROTOCOL`<\/span> (default <span>`ssl`<\/span>), <span>`CW_DESIGNAPI_TIMEOUT_S`<\/span>\n<\/span><span><span>-<\/span> <span>`CW_DESIGNAPI_CA_FILE`<\/span>, <span>`CW_DESIGNAPI_CLIENT_CERT_FILE`<\/span>,\n<\/span><span> <span>`CW_DESIGNAPI_CLIENT_KEY_FILE`<\/span>\n<\/span><span><span>-<\/span> <span>`CW_DESIGNAPI_USERNAME`<\/span> and <span>`CW_DESIGNAPI_PASSWORD_FILE`<\/span>, or\n<\/span><span> <span>`CW_DESIGNAPI_TOKEN_FILE`<\/span>, only when the verified SDK requires them\n<\/span><span><span>-<\/span> <span>`CW_DEFAULT_PLAN`<\/span>, <span>`CW_PLAN_ROOT`<\/span>, <span>`CW_STAGING_DIR`<\/span>, <span>`CW_MAX_PLAN_BYTES`<\/span>\n<\/span><span><span>-<\/span> <span>`MCP_HOST`<\/span> (default <span>`127.0.0.1`<\/span>), <span>`MCP_PORT`<\/span>, <span>`MCP_PATH`<\/span>, <span>`MCP_API_TOKEN`<\/span>\n<\/span><span>Never put secrets, certificates, or private keys in source code or CLI\n<\/span><span>arguments. Use the SDK\u2019s documented connection settings. Do not disable TLS\n<\/span><span>verification.\n<\/span><span><span>`main()`<\/span> accepts <span>`--transport {stdio,http}`<\/span> (default <span>`stdio`<\/span>), <span>`--host`<\/span>,\n<\/span><span><span>`--port`<\/span>, <span>`--path`<\/span>, <span>`--allow-remote`<\/span>, <span>`--designapi-host`<\/span>, and <span>`--cariden-home`<\/span>.\n<\/span><span>HTTP must refuse to start without <span>`MCP_API_TOKEN`<\/span>. Compare that token in\n<\/span><span>constant time. Binding outside loopback requires <span>`--allow-remote`<\/span>; document\n<\/span><span>that remote use belongs behind TLS. Stdio uses one fixed local owner. HTTP\n<\/span><span>uses one owner derived from the configured token.\n<\/span><span>IceSSL (protocol <span>`ssl`<\/span>) loads mTLS exclusively from\n<\/span><span><span>`CARIDEN_HOME\/etc\/certs\/{ca_cert.pem,designapi_user_cert.pem,designapi_user_key.pem}`<\/span>\n<\/span><span>via <span>`ServiceConnectionManager`<\/span>. Do not pass PEM contents into <span>`open_plan`<\/span>.\n<\/span><span><span>`ServiceConnectionManager.newServiceConnection(host, port, protocol)`<\/span> accepts\n<\/span><span><span>`ssl`<\/span> or <span>`tcp`<\/span>, not the <span>`open_plan`<\/span> docstring values <span>`http`<\/span>\/<span>`https`<\/span>. Default\n<\/span><span><span>`CW_DESIGNAPI_PROTOCOL`<\/span> is <span>`ssl`<\/span>.\n<\/span><span>Check configured certificate files for presence without printing their\n<\/span><span>contents. If the IceSSL private key exists and is owner-readable but not\n<\/span><span>mode <span>`0600`<\/span>\/<span>`0400`<\/span>, warn on stderr and continue; refuse only if it is\n<\/span><span>missing, unreadable, or world-writable. Tell operators to inspect each\n<\/span><span>public certificate with:\n<\/span><span><span><span>```<\/span><span>bash<\/span>\n<\/span><\/span><span><span><span>openssl x509 <span>-text<\/span> <span>-noout<\/span> <span>-in<\/span> <span>&lt;<\/span>certificate_file<span>&gt;<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span>They must check validity dates, RSA 2048-bit or P-256-or-stronger keys, a SHA-2\n<\/span><span>signature, and whether a self-signed certificate is intentional and limited to\n<\/span><span>private use. If <span>`LD_LIBRARY_PATH`<\/span> includes the SDK <span>`lib`<\/span> directory, invoke\n<\/span><span>openssl as <span>`env -u LD_LIBRARY_PATH openssl...`<\/span> \u2014 Cisco SSL libraries break\n<\/span><span>the system openssl CLI (<span>`undefined symbol: EVP_sm3`<\/span>).\n<\/span><span><span><span>##<\/span> Uploaded plans<\/span>\n<\/span><span>Keep a JSON index in the staging directory, guarded by a lock. Store plan ID,\n<\/span><span>safe filename, path, size, SHA-256 digest, upload time, and owner. Accept UTF-8\n<\/span><span>text or strict base64, enforce the size limit, and allow only safe filename\n<\/span><span>characters. Generate plan IDs on the server. Large uploads and deletions return\n<\/span><span>a preview until the caller retries with <span>`confirm=true`<\/span>.\n<\/span><span>References use <span>`upload:&lt;plan_id&gt;`<\/span>. An owned bare <span>`plan_id`<\/span> must resolve the\n<\/span><span>same way; do not treat a matching upload id as a filesystem path. List, get,\n<\/span><span>and delete only the current owner\u2019s plans, using the same not-found response\n<\/span><span>for missing and unauthorized IDs. A configured default plan is available only to the local stdio owner and\n<\/span><span>must stay inside <span>`CW_PLAN_ROOT`<\/span>. Reject path traversal and symlinks. HTTP\n<\/span><span>callers cannot pass filesystem paths.\n<\/span><span>Serialize all SDK work with one lock. Open a fresh plan context per operation\n<\/span><span>and do not modify the stored upload. Do not wrap every SDK exception as a\n<\/span><span>certificate\/DesignAPI connectivity error. Include the real exception class and\n<\/span><span>a short message (never a stack trace). Ice <span>`ValueError`<\/span> from a bad argument\n<\/span><span>is a programming\/API-usage error, not a missing cert.\n<\/span><span><span><span>##<\/span> Verified OPM\/Design operational contract<\/span>\n<\/span><span>Use these patterns. They are required for a functional server on DesignAPI\n<\/span><span>IceSSL.\n<\/span><span><span><span>**<\/span><span>Open and simulate<\/span><span>**<\/span><\/span>\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span><span>with<\/span> open_plan<span>(<\/span>path<span>,<\/span> host<span>=<\/span>host<span>,<\/span> port<span>=<\/span>port<span>,<\/span> protocol<span>=<\/span><span>\"ssl\"<\/span><span>,<\/span> linger<span>=<\/span><span>False<\/span><span>)<\/span> <span>as<\/span> network<span>:<\/span>\n<\/span><\/span><\/span><span><span><span> model <span>=<\/span> network<span>.<\/span>model\n<\/span><\/span><\/span><span><span><span> model<span>.<\/span>route_simulation <span>=<\/span> <span>[<\/span><span>]<\/span> <span># empty failure set; never assign None<\/span>\n<\/span><\/span><\/span><span><span><span> model<span>.<\/span>traffic_simulation <span>=<\/span> <span>None<\/span>\n<\/span><\/span><\/span><span><span><span> model<span>.<\/span>route_simulation<span>.<\/span>recompute<span>(<\/span><span>)<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span>Assigning <span>`model.route_simulation = None`<\/span> leaves <span>`_failed_objects`<\/span> as a list.\n<\/span><span>Then <span>`circuit.failed = True`<\/span> crashes (<span>`list`<\/span> has no <span>`.add`<\/span>). Do not use\n<\/span><span><span>`.failed = True`<\/span> for what-if. Apply failures with:\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span>model<span>.<\/span>route_simulation <span>=<\/span> <span>[<\/span>circuit<span>,<\/span> <span>.<\/span><span>.<\/span><span>.<\/span><span>]<\/span> <span># OPM circuit objects<\/span>\n<\/span><\/span><\/span><span><span><span>model<span>.<\/span>route_simulation<span>.<\/span>recompute<span>(<\/span><span>)<\/span>\n<\/span><\/span><\/span><span><span><span>_ <span>=<\/span> model<span>.<\/span>traffic_simulation <span># trigger traffic sim<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span><span><span>**<\/span><span>IGP \/ shortest path \u2014 Node objects, not name strings<\/span><span>**<\/span><\/span>\n<\/span><span><span>`RouteSimulation.shortest_path`<\/span> requires OPM <span>`Node`<\/span> objects. Passing a\n<\/span><span>string makes Ice raise `ValueError: invalid value for argument 1 in\n<\/span><span>operation getShortestPathRouteRecord` and can poison the pooled Ice\n<\/span><span>connection for later tools.\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span>src <span>=<\/span> model<span>.<\/span>nodes<span>[<\/span>source_name<span>]<\/span>\n<\/span><\/span><\/span><span><span><span>dst <span>=<\/span> model<span>.<\/span>nodes<span>[<\/span>destination_name<span>]<\/span>\n<\/span><\/span><\/span><span><span><span>route <span>=<\/span> model<span>.<\/span>route_simulation<span>.<\/span>shortest_path<span>(<\/span>src<span>,<\/span> dst<span>,<\/span> <span>\"igp\"<\/span><span>)<\/span> <span># or te\/latency\/bgp<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span><span><span>**<\/span><span>Order hops from topology, not from a Python set<\/span><span>**<\/span><\/span>\n<\/span><span><span>`route.interfaces`<\/span> is unordered. Reconstruct hop order by walking from the\n<\/span><span>source node: pick the interface whose <span>`node.name`<\/span> is the current node, then\n<\/span><span>advance to <span>`opposite_interface.node`<\/span> (or the other circuit endpoint). Use\n<\/span><span>the same walk for IGP fill between node SIDs and from the last SID to the\n<\/span><span>destination. Return that walk as <span>`resolved_ordered_path`<\/span> (list of\n<\/span><span><span>`\"{node}:{interface}\"`<\/span> strings). Do not skip IGP fill just because the SDK\n<\/span><span>collection is unordered.\n<\/span><span><span><span>**<\/span><span>Circuit identity<\/span><span>**<\/span><\/span>\n<\/span><span>Accept all of: unique <span>`node_a`<\/span>+<span>`node_b`<\/span> (reject if parallel circuits), the\n<\/span><span>SDK key <span>`ckt{n1|if1|n2|if2}`<\/span>, and the shorthand <span>`n1-n2`<\/span> \/ <span>`n2-n1`<\/span>.\n<\/span><span><span><span>**<\/span><span>Worst-case Simulation Analysis<\/span><span>**<\/span><\/span>\n<\/span><span>Index by <span><span>**<\/span><span>model<\/span><span>**<\/span><\/span> interface, not by iterating SA wrapper objects as if they\n<\/span><span>were model interfaces:\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span>sa <span>=<\/span> SimulationAnalysis<span>(<\/span>model<span>,<\/span> failure_types<span>=<\/span><span>[<\/span><span>\"circuits\"<\/span><span>]<\/span><span>,<\/span> max_fail_per_int<span>=<\/span><span>10<\/span><span>)<\/span>\n<\/span><\/span><\/span><span><span><span>sa<span>.<\/span>cache_valid <span>=<\/span> <span>True<\/span>\n<\/span><\/span><\/span><span><span><span><span>for<\/span> iface <span>in<\/span> model<span>.<\/span>interfaces<span>:<\/span>\n<\/span><\/span><\/span><span><span><span> rec <span>=<\/span> sa<span>.<\/span>interfaces<span>[<\/span>iface<span>]<\/span>\n<\/span><\/span><\/span><span><span><span> util <span>=<\/span> rec<span>.<\/span>worst_case_utilization <span># e.g. 113.39<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span>Return a stable <span>`interface_id`<\/span> such as <span>`if{node|name}`<\/span> or <span>`{node}:{name}`<\/span>,\n<\/span><span>never a Python <span>`repr`<\/span> of a wrapper object.\n<\/span><span><span><span>**<\/span><span>Traffic growth (<span>`create_growth_plans`<\/span>)<\/span><span>**<\/span><\/span>\n<\/span><span>JSON options are kebab-case. Use the DesignAPI filesystem round-trip:\n<\/span><span><span>1.<\/span> Set <span>`demand.growth_percent`<\/span> on active demands.\n<\/span><span><span>2.<\/span> <span>`network.rpc_plan.serializeToFileSystem(\"\/tmp\/mcp_growth_&lt;unique&gt;.txt\")`<\/span>\n<\/span><span> on the DesignAPI host (a unique path each call).\n<\/span><span><span>3.<\/span> <span>`newPlanFromFileSystem`<\/span> that path; `newGenericTool().runTool(net,\n<\/span><span> \"create_growth_plans\", json.dumps({...}))` with:\n<\/span><span><span><span>```<\/span><span>json<\/span>\n<\/span><\/span><span><span><span><span>{<\/span>\n<\/span><\/span><\/span><span><span><span> <span>\"plan-file\"<\/span><span>:<\/span> <span>\"&lt;same server path&gt;\"<\/span><span>,<\/span>\n<\/span><\/span><\/span><span><span><span> <span>\"create-plans-from\"<\/span><span>:<\/span> <span>\"DEMANDS\"<\/span><span>,<\/span>\n<\/span><\/span><\/span><span><span><span> <span>\"num-periods\"<\/span><span>:<\/span> <span>1<\/span><span>,<\/span>\n<\/span><\/span><\/span><span><span><span> <span>\"period-inc\"<\/span><span>:<\/span> <span>1<\/span><span>,<\/span>\n<\/span><\/span><\/span><span><span><span> <span>\"growth-method\"<\/span><span>:<\/span> <span>\"COMPOUND\"<\/span><span>,<\/span>\n<\/span><\/span><\/span><span><span><span> <span>\"run-sim-analysis\"<\/span><span>:<\/span> <span>false<\/span>\n<\/span><\/span><\/span><span><span><span><span>}<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span><span>4.<\/span> If the tool <span><span>**<\/span><span>raises<\/span><span>**<\/span><\/span> or succeeds without changing demand traffic, apply\n<\/span><span> compound <span>`(1 + growth\/100) ** (period_inc * num_periods)`<\/span> through the\n<\/span><span> documented demand-traffic API (<span>`Demand.traffic`<\/span> or <span>`DemandTrafficKey`<\/span>) and\n<\/span><span> set <span>`fallback_used: true`<\/span>. Do not treat a tool exception as a hard failure\n<\/span><span> when the fallback can still produce a correct COMPOUND result.\n<\/span><span><span>5.<\/span> Delete local and server-side temp plans in <span>`finally`<\/span>.\n<\/span><span><span>6.<\/span> Reject <span>`SIMPLE`<\/span> unless you implement its documented linear math; never\n<\/span><span> label compound arithmetic as SIMPLE.\n<\/span><span><span><span>##<\/span> MCP surface<\/span>\n<\/span><span>Server instructions must say: upload a plan before simulation; use resources\n<\/span><span>for unfiltered reads; call <span>`failure_sim`<\/span> only for a known circuit failure; call\n<\/span><span><span>`get_wc_traffic`<\/span> alone for worst-case analysis; call <span>`list_lsps`<\/span> before\n<\/span><span><span>`get_lsp_path`<\/span>; use <span>`resolved_ordered_path`<\/span> for ordered LSP hops because the\n<\/span><span>route-simulation interface set is unordered.\n<\/span><span>Resources, all JSON:\n<\/span><span><span>1.<\/span> <span>`server:\/\/health`<\/span> \u2014 SDK path, DesignAPI endpoint, certificate-file presence, and reachability.\n<\/span><span><span>2.<\/span> <span>`plan:\/\/uploads`<\/span> \u2014 current owner\u2019s plans.\n<\/span><span><span>3.<\/span> <span>`plan:\/\/{plan_id}\/summary`<\/span> \u2014 node, circuit, demand, and LSP counts, including active counts.\n<\/span><span><span>4.<\/span> <span>`plan:\/\/{plan_id}\/circuits`<\/span> \u2014 bounded active circuits.\n<\/span><span><span>5.<\/span> <span>`plan:\/\/{plan_id}\/demands`<\/span> \u2014 bounded active demands.\n<\/span><span>Prompts:\n<\/span><span><span>1.<\/span> <span>`link_failure_workflow(node_a, node_b)`<\/span> \u2014 upload, inspect circuits, run <span>`failure_sim`<\/span>, review reroutes and new oversubscriptions, then inspect affected demand paths.\n<\/span><span><span>2.<\/span> <span>`capacity_planning_workflow(growth_percent=33.0)`<\/span> \u2014 upload, read the summary, run growth, run circuit worst-case analysis, and summarize oversubscribed and near-capacity circuits.\n<\/span><span>Tools:\n<\/span><span><span>1.<\/span> <span>`upload_plan(name, content, encoding=\"text\", confirm=False)`<\/span> \u2014 store the plan, open it with OPM, and return its ID, <span>`upload:`<\/span> reference, metadata, and model counts. Delete the staged files if opening fails.\n<\/span><span><span>2.<\/span> <span>`list_uploaded_plans()`<\/span>.\n<\/span><span><span>3.<\/span> <span>`delete_uploaded_plan(plan_id, confirm=False)`<\/span>.\n<\/span><span><span>4.<\/span> <span>`designapi_health_check(plan_ref=None)`<\/span>.\n<\/span><span><span>5.<\/span> <span>`get_plan_summary(plan_ref=None)`<\/span>.\n<\/span><span><span>6.<\/span> <span>`list_circuits(plan_ref=None, node_filter=None, active_only=True, limit=100)`<\/span> \u2014 endpoints, interfaces, capacity, and active state. Cap <span>`limit`<\/span> and report truncation.\n<\/span><span><span>7.<\/span> <span>`list_demands(plan_ref=None, source=None, destination=None, active_only=True, limit=100)`<\/span> \u2014 name, endpoints, service class, traffic, and active state.\n<\/span><span><span>8.<\/span> <span>`get_demand_path(source, destination, plan_ref=None, demand_name=None, service_)`<\/span> \u2014 clear failures, recompute the baseline, and return routed state, traffic, metric, latency, ECMP percentage, interfaces, traffic shares, and hop count.\n<\/span><span><span>9.<\/span> <span>`get_igp_path(source, destination, plan_ref=None, metric=\"igp\")`<\/span> \u2014 allow only metrics supported by the installed SDK. Resolve endpoints with <span>`model.nodes[name]`<\/span> and call <span>`shortest_path`<\/span> with those Node objects. Return interfaces and hop count.\n<\/span><span><span>10.<\/span> <span>`list_lsps(plan_ref=None, source=None, destination=None, active_only=True, limit=100)`<\/span> \u2014 type, setup bandwidth, color, state, and path options.\n<\/span><span><span>11.<\/span> <span>`get_lsp_path(source, plan_ref=None, lsp_name=None, destination=None, color=None, destination_ip=None, path_option=None)`<\/span> \u2014 resolve one RSVP-TE LSP by source and name, or one SR policy by source, destination, and color. Recompute the baseline. Return route metrics and these separate fields: configured <span>`segment_list_hops`<\/span>, ordered <span>`resolved_ordered_path`<\/span> as <span>`\"{node}:{iface}\"`<\/span> strings (interface SID directly; node SID filled by IGP using Node-object <span>`shortest_path`<\/span> plus the topology walk; then IGP fill to the destination), unordered <span>`interface_set`<\/span>, and comparison-only <span>`reference_igp_shortest_path`<\/span> (also Node-object <span>`shortest_path`<\/span>). Warn on unresolved segment steps. Do not present an unordered set as hop order; do not omit IGP fill because the SDK collection is unordered.\n<\/span><span><span>12.<\/span> <span>`failure_sim(plan_ref=None, node_a=None, node_b=None, failed_circuits=None, top_n=25, min_traffic_delta_mbps=1.0, sample_reroutes=5)`<\/span> \u2014 fail one uniquely matched circuit between two nodes, or the listed circuits (<span>`ckt{...}`<\/span> or <span>`nodeA-nodeB`<\/span>). Capture the baseline, apply failures with <span>`model.route_simulation = [circuits]`<\/span>, recompute, and return failed IDs, rerouted and unrouted counts, a bounded reroute sample, the largest interface traffic changes, and new oversubscriptions. This is a specific what-if, not worst-case analysis.\n<\/span><span><span>13.<\/span> <span>`get_wc_traffic(plan_ref=None, failure_sets=\"nodes,circuits\", max_fail_per_int=10, limit=15)`<\/span> \u2014 validate failure-set names, run Cisco <span>`SimulationAnalysis`<\/span>, index <span>`sa.interfaces[model_iface]`<\/span>, and return the top interfaces by vendor worst-case utilization with stable interface ids. Do not loop over <span>`failure_sim`<\/span>.\n<\/span><span><span>14.<\/span> <span>`get_traffic_growth(plan_ref=None, growth_percent=33.0, num_periods=1, period_inc=1, growth_method=\"COMPOUND\", threshold=100.0, near_capacity=80.0)`<\/span> \u2014 record baseline circuit utilization, run kebab-case <span>`create_growth_plans`<\/span> via the filesystem round-trip above, and report oversubscribed and near-capacity circuits. If that tool raises or leaves traffic unchanged, apply the compound multiplier through the documented demand-traffic API and set <span>`fallback_used`<\/span>. Reject <span>`SIMPLE`<\/span> unless its documented linear math is implemented. Clean up every temporary file and temporary Design plan on both success and failure.\n<\/span><span>Reset simulation state before each baseline and recompute after failures. Treat missing utilization as unknown. Compare both sides of a circuit and report the higher-utilization side. Cap list and sample sizes.\n<\/span><span><span><span>##<\/span> Finish<\/span>\n<\/span><span>If a required API was missing, return only the blocked report. Otherwise compile the file and confirm that all 5 resource URIs, 2 prompts, and 14 tools are registered, that stdout is unused for logging, and that the source contains no secrets or certificate material. Confirm <span>`os.execv`<\/span> (or equivalent) is used so <span>`python3 cp_sample_mcp.py`<\/span> works without a pre-exported <span>`LD_LIBRARY_PATH`<\/span>. Run any small offline checks you can.\n<\/span><span>If <span>`CW_DESIGNAPI_HOST`<\/span> is set and <span>`us_wan.txt`<\/span> is next to the script, you <span><span>**<\/span><span>must<\/span><span>**<\/span><\/span> run live DesignAPI checks before finishing: upload the plan; <span>`get_demand_path`<\/span> er1.sjc\u2192er1.mia (expect hop_count 6, metric <span><span>~<\/span><span>2169); <span>`get_igp_path`<\/span> cr1.sjc\u2192cr1.kcy (non-empty, no Ice ValueError); <span>`failure_sim`<\/span> node pair cr1.sjc\/cr1.kcy (rerouted_count 10); <span>`failure_sim`<\/span> <span>`failed_circuits=[\"cr1.sjc-cr1.kcy\"]`<\/span>; <span>`get_wc_traffic`<\/span> circuits (top util <\/span><span>~<\/span><\/span>113, human-readable interface ids); <span>`get_traffic_growth`<\/span> 33% one period (<span>`ok`<\/span> true, either tool growth or <span>`fallback_used`<\/span>). Fix failures before answering. State any live checks that still could not run.\n<\/span><\/code><\/pre>\n<\/details>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Appendix B: Full build prompt<\/h2>\n<p>This is the full prompt from Step 8. It is also available as <a href=\"https:\/\/github.com\/flim226\/crosswork\/blob\/main\/sample-scripts\/cp_sample_mcp\/generation_prompt.md\" style=\"color: rgb(0, 102, 204)\"><code>generation_prompt.md<\/code><\/a>.<\/p>\n<details>\n<summary>Show the full prompt<\/summary>\n<pre><code><span><span><span>#<\/span> One-shot build prompt: Crosswork Planning MCP server<\/span>\n<\/span><span>You are a senior Python platform engineer with deep experience in the Model\n<\/span><span>Context Protocol (MCP), FastMCP, Cisco Crosswork Planning, the OPM Python API,\n<\/span><span>the Design RPC API, secure service design, and Pydantic v2.\n<\/span><span>Build a production-quality Crosswork Planning simulation MCP server. The result\n<\/span><span>must be complete, runnable, secure by default, and useful to an MCP client\n<\/span><span>without manual code completion.\n<\/span><span><span><span>##<\/span> Non-negotiable deliverable<\/span>\n<\/span><span>Create exactly one implementation file named <span>`cp_sample_mcp.py`<\/span>.\n<\/span><span>Name the FastMCP server uniquely (<span>`cw-planning-tutorial`<\/span> unless that name is\n<\/span><span>already taken in the MCP client). Do not reuse <span>`cp_sample_mcp`<\/span> or\n<\/span><span><span>`cp-sample-sim`<\/span> if another server already uses them.\n<\/span><span><span>-<\/span> Target Python 3.11 or newer.\n<\/span><span><span>-<\/span> Use <span>`fastmcp.FastMCP`<\/span> and Pydantic v2.\n<\/span><span><span>-<\/span> Include <span>`#!\/usr\/bin\/env python3`<\/span>, a concise module docstring, local stdio and\n<\/span><span> authenticated HTTP launch examples, dependency expectations, and all\n<\/span><span> environment-variable names.\n<\/span><span><span>-<\/span> Use type hints throughout and Ruff\/Black-compatible formatting.\n<\/span><span><span>-<\/span> Do not emit pseudocode, TODOs, ellipses, mock production behavior, omitted\n<\/span><span> handlers, or references to code outside the file.\n<\/span><span><span>-<\/span> Do not create supporting Python modules. Runtime state files and temporary\n<\/span><span> plan files are allowed.\n<\/span><span><span>-<\/span> If operating in a coding workspace, write the file and validate it there. If\n<\/span><span> only text output is possible, return the entire source in one fenced block.\n<\/span><span>This build has one prerequisite gate: the installed FastMCP and Cisco SDK must\n<\/span><span>expose enough documented API to implement all 14 tools without invented calls.\n<\/span><span>If that gate fails, do not create a partial or deceptive server. Return a\n<\/span><span>concise <span>`status: blocked`<\/span> evidence report naming the paths, package versions,\n<\/span><span>documentation, and required symbols checked. A capability that is present in\n<\/span><span>the SDK but unavailable at runtime because of licensing, credentials, or a\n<\/span><span>temporarily unreachable DesignAPI does not block generation; keep its tool\n<\/span><span>registered and return a safe <span>`CAPABILITY_UNAVAILABLE`<\/span> error at runtime.\n<\/span><span><span><span>##<\/span> Establish API ground truth before coding<\/span>\n<\/span><span>The Cisco APIs are release-sensitive. Inspect the installed SDK, its Python\n<\/span><span>source\/type stubs, bundled examples, and locally available official\n<\/span><span>documentation before writing integration code. Training-memory guesses are not\n<\/span><span>acceptable evidence.\n<\/span><span><span>1.<\/span> Determine <span>`CARIDEN_HOME`<\/span> in this precedence order: CLI override,\n<\/span><span> environment variable, then a configurable local default.\n<\/span><span><span>2.<\/span> Apply CLI overrides before importing any Cisco package.\n<\/span><span><span>3.<\/span> Verify <span>`&lt;CARIDEN_HOME&gt;\/lib\/python`<\/span>, add it to <span>`sys.path`<\/span>, and put both\n<\/span><span> <span>`&lt;CARIDEN_HOME&gt;\/lib`<\/span> and <span>`&lt;CARIDEN_HOME&gt;\/lib\/python`<\/span> on <span>`LD_LIBRARY_PATH`<\/span>.\n<\/span><span> Mutating <span>`os.environ[\"LD_LIBRARY_PATH\"]`<\/span> in an already-started process does\n<\/span><span> not load <span>`libIceDiscovery.so`<\/span>. After updating native paths, if <span>`Ice`<\/span>\/<span>`IcePy`<\/span>\n<\/span><span> cannot import, re-exec the same interpreter with <span>`os.execv`<\/span> (preserving argv\n<\/span><span> and the new env) <span><span>**<\/span><span>before<\/span><span>**<\/span><\/span> any Cisco import. Then inspect the installed\n<\/span><span> symbols.\n<\/span><span><span>4.<\/span> Verify every imported Cisco class and every constructor, attribute, method,\n<\/span><span> enum, RPC option, and return type used by the implementation.\n<\/span><span><span>5.<\/span> At minimum, establish whether this release provides and how it uses:\n<\/span><span> <span>`open_plan`<\/span>, <span>`SimulationAnalysis`<\/span>, <span>`PlanKey`<\/span>, <span>`PlanFormat`<\/span>,\n<\/span><span> <span>`DemandTrafficKey`<\/span>, the Design plan manager, the Design generic-tool\n<\/span><span> manager, plan serialization, route simulation, traffic simulation, LSP\n<\/span><span> keys, LSP paths, and segment lists.\n<\/span><span><span>6.<\/span> Keep all version-sensitive Cisco operations in a small, clearly marked\n<\/span><span> compatibility-adapter section. Comments may state the installed versions\n<\/span><span> and the verified alternatives, but must not claim support that was not\n<\/span><span> checked.\n<\/span><span><span>7.<\/span> Never invent an API or silently replace a missing Cisco analysis with a\n<\/span><span> home-grown approximation. Apply the prerequisite gate above when an API is\n<\/span><span> absent; use a runtime capability error only when the API exists but the\n<\/span><span> deployed service cannot currently execute it.\n<\/span><span><span>8.<\/span> Python docstrings in this SDK are incomplete and sometimes wrong.\n<\/span><span> <span>`open_plan`<\/span>'s protocol text (<span>`http`<\/span>\/<span>`https`<\/span>) does not match\n<\/span><span> <span>`ServiceConnectionManager.newServiceConnection`<\/span>, which accepts <span>`ssl`<\/span> or\n<\/span><span> <span>`tcp`<\/span>. Remote IceSSL DesignAPI uses <span>`protocol=\"ssl\"`<\/span>. GenericTool JSON\n<\/span><span> option names are not Python symbols; they appear as kebab-case strings in\n<\/span><span> <span>`libdesignservice.so`<\/span>. Use the <span><span>**<\/span><span>Verified OPM\/Design operational contract<\/span><span>**<\/span><\/span>\n<\/span><span> below as mandatory ground truth for those items. Do not invent camelCase\n<\/span><span> GenericTool options.\n<\/span><span>Use FastMCP APIs from one installed distribution\/version only. Verify decorator,\n<\/span><span>middleware, authentication, lifespan, context, transport, and <span>`run()`<\/span> signatures\n<\/span><span>instead of mixing examples from different FastMCP releases.\n<\/span><span><span><span>##<\/span> Server purpose and client instructions<\/span>\n<\/span><span>Create one FastMCP server for uploading a Crosswork Planning plan and performing\n<\/span><span>topology inspection, baseline route inspection, LSP\/SR-policy path inspection,\n<\/span><span>deterministic circuit-failure simulation, worst-case Simulation Analysis, and\n<\/span><span>traffic-growth forecasting.\n<\/span><span>The server instructions must tell clients to:\n<\/span><span><span>-<\/span> upload a user-supplied plan first and pass the returned canonical plan\n<\/span><span> reference to later operations;\n<\/span><span><span>-<\/span> prefer MCP resources for unfiltered, read-only data;\n<\/span><span><span>-<\/span> use <span>`failure_sim`<\/span> only for a specifically identified circuit\/link failure;\n<\/span><span><span>-<\/span> use <span>`get_wc_traffic`<\/span> alone for worst-case or Simulation Analysis questions;\n<\/span><span><span>-<\/span> call <span>`list_lsps`<\/span> before <span>`get_lsp_path`<\/span>;\n<\/span><span><span>-<\/span> treat a route-simulation interface collection as unordered unless the SDK\n<\/span><span> explicitly guarantees order, and use resolved segment-list expansion for\n<\/span><span> ordered LSP\/SR-policy hops.\n<\/span><span><span><span>##<\/span> Configuration and startup<\/span>\n<\/span><span>Implement one typed central configuration model. Reject malformed values and\n<\/span><span>unknown keys only within the application's own config namespace; ignore\n<\/span><span>unrelated process environment variables. Support these environment variables:\n<\/span><span><span>-<\/span> <span>`CARIDEN_HOME`<\/span>;\n<\/span><span><span>-<\/span> <span>`CW_DESIGNAPI_HOST`<\/span>, <span>`CW_DESIGNAPI_PORT`<\/span>, <span>`CW_DESIGNAPI_PROTOCOL`<\/span>,\n<\/span><span> <span>`CW_DESIGNAPI_TIMEOUT_S`<\/span>, <span>`CW_DESIGNAPI_CA_FILE`<\/span>,\n<\/span><span> <span>`CW_DESIGNAPI_CLIENT_CERT_FILE`<\/span>, <span>`CW_DESIGNAPI_CLIENT_KEY_FILE`<\/span>, and\n<\/span><span> <span>`CW_DESIGNAPI_SERVER_NAME`<\/span>;\n<\/span><span><span>-<\/span> <span>`CW_DESIGNAPI_USERNAME`<\/span>, <span>`CW_DESIGNAPI_PASSWORD_FILE`<\/span>, and\n<\/span><span> <span>`CW_DESIGNAPI_TOKEN_FILE`<\/span> when the verified SDK uses password or token\n<\/span><span> authentication rather than mTLS; secret files must have private permissions,\n<\/span><span> and mutually exclusive authentication modes must be rejected;\n<\/span><span><span>-<\/span> <span>`CW_DEFAULT_PLAN`<\/span>, <span>`CW_PLAN_ROOT`<\/span>, <span>`CW_STAGING_DIR`<\/span>,\n<\/span><span> <span>`CW_MAX_PLAN_BYTES`<\/span>, <span>`CW_LARGE_UPLOAD_CONFIRM_BYTES`<\/span>,\n<\/span><span> <span>`CW_OWNER_QUOTA_BYTES`<\/span>, and <span>`CW_PLAN_TTL_HOURS`<\/span>;\n<\/span><span><span>-<\/span> <span>`MCP_HOST`<\/span>, <span>`MCP_PORT`<\/span>, <span>`MCP_PATH`<\/span>, <span>`MCP_RATE_LIMIT_RPS`<\/span>, and\n<\/span><span> <span>`MCP_RATE_LIMIT_BURST`<\/span>;\n<\/span><span><span>-<\/span> <span>`MCP_TOKEN_FILE`<\/span>, a root\/user-readable-only JSON secret file mapping opaque\n<\/span><span> bearer tokens to stable owner IDs.\n<\/span><span>If the installed Cisco release obtains DesignAPI mTLS settings exclusively from\n<\/span><span>its own documented configuration, verify and use that mechanism instead of\n<\/span><span>inventing constructor arguments. This release loads IceSSL from\n<\/span><span><span>`CARIDEN_HOME\/etc\/certs\/{ca_cert.pem,designapi_user_cert.pem,designapi_user_key.pem}`<\/span>\n<\/span><span>inside <span>`ServiceConnectionManager`<\/span> (<span>`IceSSL.VerifyPeer=2`<\/span>). Environment values\n<\/span><span>above may point to trust and identity files but must never contain private-key\n<\/span><span>or certificate contents. Do not accept secrets through CLI arguments. Default\n<\/span><span><span>`CW_DESIGNAPI_PORT`<\/span> is <span>`30744`<\/span>. Default <span>`CW_DESIGNAPI_PROTOCOL`<\/span> is <span>`ssl`<\/span>.\n<\/span><span>Also accept <span>`--designapi-host`<\/span> as an alias of <span>`--cw-host`<\/span>.\n<\/span><span>Use safe defaults:\n<\/span><span><span>-<\/span> stdio transport;\n<\/span><span><span>-<\/span> HTTP bound to <span>`127.0.0.1`<\/span>;\n<\/span><span><span>-<\/span> verified TLS for DesignAPI;\n<\/span><span><span>-<\/span> no deployment-specific Crosswork address;\n<\/span><span><span>-<\/span> private filesystem permissions;\n<\/span><span><span>-<\/span> no embedded password, token, private key, certificate, secret, or credentialed\n<\/span><span> connection string.\n<\/span><span>Provide <span>`main()`<\/span> with at least:\n<\/span><span><span>-<\/span> <span>`--transport {stdio,http}`<\/span>, default <span>`stdio`<\/span>;\n<\/span><span><span>-<\/span> <span>`--host`<\/span>, <span>`--port`<\/span>, and <span>`--path`<\/span>;\n<\/span><span><span>-<\/span> <span>`--allow-remote`<\/span>;\n<\/span><span><span>-<\/span> <span>`--cw-host`<\/span> and <span>`--designapi-host`<\/span> (aliases for the DesignAPI host);\n<\/span><span><span>-<\/span> <span>`--cariden-home`<\/span>;\n<\/span><span><span>-<\/span> a non-network <span>`--check`<\/span> or equivalent startup diagnostic mode.\n<\/span><span>Parse and apply arguments that affect SDK discovery before SDK imports. Startup\n<\/span><span>must validate configuration and required capabilities, reconcile\/purge stale\n<\/span><span>uploads, and print only sanitized diagnostics to stderr. Stdio stdout must\n<\/span><span>remain exclusively available to the MCP protocol.\n<\/span><span>HTTP transport must fail closed unless <span>`MCP_TOKEN_FILE`<\/span> is configured, has\n<\/span><span>private permissions, contains at least one valid token-to-owner mapping, and is\n<\/span><span>handled by a production-capable verifier supported by the installed FastMCP\n<\/span><span>release. Accept tokens only through the Authorization Bearer scheme. Compare\n<\/span><span>candidate tokens in constant time and derive the stable owner ID only from the\n<\/span><span>matched server-side mapping, never from tool arguments. Do not log or echo the\n<\/span><span>mapping. A shared token intentionally represents one owner; different owners\n<\/span><span>must have different tokens. For stdio, use one explicit constant local\n<\/span><span>principal for that server process.\n<\/span><span>Require an explicit flag for non-loopback binding, enable host\/origin\n<\/span><span>protection, apply per-owner rate limiting, and configure a concrete\n<\/span><span>request-body limit at the HTTP server or documented reverse-proxy boundary.\n<\/span><span>Non-loopback deployment must refuse to start unless direct TLS is configured\n<\/span><span>through supported FastMCP settings or an explicit trusted-reverse-proxy mode is\n<\/span><span>enabled. Do not trust forwarded identity headers. Do not use a debug\n<\/span><span>authentication provider in production.\n<\/span><span><span><span>##<\/span> Plan registry and filesystem safety<\/span>\n<\/span><span>Implement a persistent SQLite uploaded-plan registry under the staging\n<\/span><span>directory. Use transactions, foreign-key enforcement, busy timeouts, unique\n<\/span><span>constraints, and active-operation leases so publication, lookup, quota\n<\/span><span>accounting, deletion, and TTL cleanup are race-safe. Run one server process;\n<\/span><span>reject configurations that request multiple workers unless equivalent\n<\/span><span>cross-process vendor-operation coordination is implemented.\n<\/span><span>For every plan record store:\n<\/span><span><span>-<\/span> server-generated unguessable plan ID;\n<\/span><span><span>-<\/span> safe display filename;\n<\/span><span><span>-<\/span> canonical path managed by the server;\n<\/span><span><span>-<\/span> exact byte size and SHA-256 content digest;\n<\/span><span><span>-<\/span> upload timestamp and expiry;\n<\/span><span><span>-<\/span> input encoding;\n<\/span><span><span>-<\/span> authenticated owner ID;\n<\/span><span><span>-<\/span> validation state.\n<\/span><span>Required behavior:\n<\/span><span><span>-<\/span> accept UTF-8 text and strict validated base64;\n<\/span><span><span>-<\/span> validate decoded size, not encoded size;\n<\/span><span><span>-<\/span> allow only an explicitly defined safe filename and plan-ID character set;\n<\/span><span><span>-<\/span> reject path separators, traversal, NULs, control characters, symlinks,\n<\/span><span> non-regular files, and collisions;\n<\/span><span><span>-<\/span> reject impossible encoded lengths before decoding, strictly decode with a\n<\/span><span> hard decoded-size cap, enforce the HTTP request-body limit above, and do not\n<\/span><span> falsely claim handler-level decoding prevents the MCP framework from\n<\/span><span> buffering its request;\n<\/span><span><span>-<\/span> require an explicit <span>`confirm=true`<\/span> retry before a large upload;\n<\/span><span><span>-<\/span> write privately to a unique temporary file, validate exact size\/digest,\n<\/span><span> fsync, then atomically publish;\n<\/span><span><span>-<\/span> never expose a partial or failed upload;\n<\/span><span><span>-<\/span> perform owner-scoped lookup, listing, deletion, and TTL cleanup without\n<\/span><span> revealing whether another owner's ID exists;\n<\/span><span><span>-<\/span> require explicit confirmation before deletion;\n<\/span><span><span>-<\/span> make deletion and cleanup race-safe with active simulations;\n<\/span><span><span>-<\/span> recover or safely remove stale temporary state after restart;\n<\/span><span><span>-<\/span> never follow symlinks during cleanup.\n<\/span><span>Canonical uploaded references must use <span>`upload:&lt;plan_id&gt;`<\/span>; an authorized bare ID\n<\/span><span>may also be accepted. The optional <span>`plan_id`<\/span> argument may provide a custom ID\n<\/span><span>only for the trusted stdio principal; HTTP always receives a server-generated\n<\/span><span>ID. A collision must return the same generic response as any unusable ID so it\n<\/span><span>cannot reveal another owner's record. Never reuse a deleted ID.\n<\/span><span>A configured default plan is optional and available only to the trusted stdio\n<\/span><span>principal. HTTP operations must supply an owned upload reference.\n<\/span><span>Direct local filesystem references use an explicit <span>`file:&lt;path&gt;`<\/span> syntax and are\n<\/span><span>allowed only for the trusted local stdio principal. Open them relative to an\n<\/span><span>allowlisted root using <span>`openat`<\/span>\/<span>`O_NOFOLLOW`<\/span> or an equivalent race-safe\n<\/span><span>descriptor-based traversal, verify the opened descriptor with <span>`fstat`<\/span>, and copy\n<\/span><span>it into managed storage before use. Reject traversal, symlinks, hard links,\n<\/span><span>non-regular files, and path-replacement races. HTTP callers must never access\n<\/span><span>arbitrary server paths. Do not include sensitive absolute paths in remote\n<\/span><span>responses.\n<\/span><span><span><span>##<\/span> Concurrency, isolation, errors, and responses<\/span>\n<\/span><span>Assume OPM and Design RPC objects are blocking and not thread-safe. Serialize\n<\/span><span>vendor operations with a lock and use one HTTP worker unless cross-process\n<\/span><span>coordination is implemented. Acquire an active-operation lease before opening a\n<\/span><span>private working copy of a plan; deletion and expiry must wait for or safely\n<\/span><span>reject active leases. Keep each simulation inside a newly opened working\n<\/span><span>context. Never give the SDK write access to the registered baseline artifact.\n<\/span><span>Use bounded inputs and outputs. Configure vendor-supported RPC\/network\n<\/span><span>timeouts. Do not describe <span>`Future.result(timeout=...)`<\/span> or async cancellation as\n<\/span><span>stopping an in-flight native SDK call. If the SDK provides no cancellation,\n<\/span><span>report a soft caller timeout while retaining the operation lease and lock until\n<\/span><span>the worker really completes; use a killable subprocess only if SDK licensing\n<\/span><span>and object transfer are verified to support it. If blocking work is invoked\n<\/span><span>from an async handler, offload it without violating SDK thread affinity.\n<\/span><span>Return JSON-serializable dictionaries with a consistent success envelope:\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span><span>{<\/span><span>\"ok\"<\/span><span>:<\/span> <span>True<\/span><span>,<\/span> <span>.<\/span><span>.<\/span><span>.<\/span><span>}<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span>Map expected validation, lookup, SDK, filesystem, timeout, and authorization\n<\/span><span>failures to concise FastMCP <span>`ToolError`<\/span> or <span>`ResourceError`<\/span> responses with stable\n<\/span><span>safe error codes and actionable hints. Do not leak stack traces, credentials,\n<\/span><span>certificate contents, another owner's metadata, or sensitive paths. Avoid\n<\/span><span>blanket <span>`except Exception`<\/span> except at a boundary that records a safe error and\n<\/span><span>immediately re-raises or maps it.\n<\/span><span>Add structured audit logging for tools, resources, and prompts. Include target,\n<\/span><span>authenticated owner\/client identifier, correlation ID, duration, result, and\n<\/span><span>exception class. Never log plan content, bearer tokens, credentials, or\n<\/span><span>certificate contents.\n<\/span><span><span><span>##<\/span> MCP resources<\/span>\n<\/span><span>Register these read-only JSON resources:\n<\/span><span><span>1.<\/span> <span>`server:\/\/health`<\/span>\n<\/span><span> <span>-<\/span> sanitized SDK\/runtime version and capability status;\n<\/span><span> <span>-<\/span> sanitized DesignAPI endpoint;\n<\/span><span> <span>-<\/span> required certificate-file presence;\n<\/span><span> <span>-<\/span> DesignAPI reachability when a plan can be opened;\n<\/span><span> <span>-<\/span> safe error code and actionable hint.\n<\/span><span><span>2.<\/span> <span>`plan:\/\/uploads`<\/span>\n<\/span><span> <span>-<\/span> only plans owned by the current principal.\n<\/span><span><span>3.<\/span> <span>`plan:\/\/{plan_id}\/summary`<\/span>\n<\/span><span> <span>-<\/span> node, circuit, active-circuit, demand, active-demand, LSP, active-LSP, and\n<\/span><span> LSP-path counts.\n<\/span><span><span>4.<\/span> <span>`plan:\/\/{plan_id}\/circuits`<\/span>\n<\/span><span> <span>-<\/span> bounded active-circuit catalog.\n<\/span><span><span>5.<\/span> <span>`plan:\/\/{plan_id}\/demands`<\/span>\n<\/span><span> <span>-<\/span> bounded active-demand catalog.\n<\/span><span>Resource URI IDs must receive the same character validation and ownership\n<\/span><span>authorization as tool inputs.\n<\/span><span><span><span>##<\/span> MCP prompts<\/span>\n<\/span><span>Register:\n<\/span><span><span>1.<\/span> <span>`link_failure_workflow(node_a, node_b)`<\/span>\n<\/span><span> <span>-<\/span> upload if needed;\n<\/span><span> <span>-<\/span> inspect circuits;\n<\/span><span> <span>-<\/span> run the explicit failure simulation;\n<\/span><span> <span>-<\/span> review reroutes and new oversubscriptions;\n<\/span><span> <span>-<\/span> inspect affected demand paths.\n<\/span><span><span>2.<\/span> <span>`capacity_planning_workflow(growth_percent=33.0)`<\/span>\n<\/span><span> <span>-<\/span> upload if needed;\n<\/span><span> <span>-<\/span> inspect the baseline summary;\n<\/span><span> <span>-<\/span> run traffic growth;\n<\/span><span> <span>-<\/span> run worst-case circuit-failure Simulation Analysis;\n<\/span><span> <span>-<\/span> summarize oversubscribed and near-capacity circuits.\n<\/span><span><span><span>##<\/span> Verified OPM\/Design operational contract<\/span>\n<\/span><span>These patterns are mandatory. Do not replace them with docstring guesses.\n<\/span><span><span><span>**<\/span><span>Native libs.<\/span><span>**<\/span><\/span> After setting <span>`LD_LIBRARY_PATH`<\/span> to\n<\/span><span><span>`&lt;CARIDEN_HOME&gt;\/lib:&lt;CARIDEN_HOME&gt;\/lib\/python`<\/span>, re-exec with <span>`os.execv`<\/span> if\n<\/span><span><span>`IcePy`<\/span> is not yet importable. A wrapper script is allowed only as comments\n<\/span><span>in the module docstring; the Python file itself must start unaided.\n<\/span><span><span><span>**<\/span><span>Open plan.<\/span><span>**<\/span><\/span> <span>`open_plan(path, host=..., port=..., protocol=\"ssl\", linger=False)`<\/span>.\n<\/span><span>Never <span>`https`<\/span>. IceSSL files are not constructor arguments.\n<\/span><span><span><span>**<\/span><span>Baseline reset.<\/span><span>**<\/span><\/span> Always:\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span>model<span>.<\/span>route_simulation <span>=<\/span> <span>[<\/span><span>]<\/span> <span># empty failure list; never None<\/span>\n<\/span><\/span><\/span><span><span><span>model<span>.<\/span>traffic_simulation <span>=<\/span> <span>None<\/span>\n<\/span><\/span><\/span><span><span><span>model<span>.<\/span>route_simulation<span>.<\/span>recompute<span>(<\/span><span>)<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span><span>`route_simulation = None`<\/span> leaves <span>`_failed_objects`<\/span> as a list, so\n<\/span><span><span>`circuit.failed = True`<\/span> raises `AttributeError: 'list' object has no attribute\n<\/span><span>'add'<span>`. Do not use `<\/span>.failed = True` for what-if.\n<\/span><span><span><span>**<\/span><span>Apply circuit failures.<\/span><span>**<\/span><\/span>\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span>model<span>.<\/span>route_simulation <span>=<\/span> <span>[<\/span>circuit<span>,<\/span> <span>.<\/span><span>.<\/span><span>.<\/span><span>]<\/span> <span># OPM circuit objects<\/span>\n<\/span><\/span><\/span><span><span><span>model<span>.<\/span>route_simulation<span>.<\/span>recompute<span>(<\/span><span>)<\/span>\n<\/span><\/span><\/span><span><span><span>_ <span>=<\/span> model<span>.<\/span>traffic_simulation<\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span><span><span>**<\/span><span>IGP.<\/span><span>**<\/span><\/span> <span>`shortest_path`<\/span> requires OPM Node objects:\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span>route <span>=<\/span> model<span>.<\/span>route_simulation<span>.<\/span>shortest_path<span>(<\/span>\n<\/span><\/span><\/span><span><span><span> model<span>.<\/span>nodes<span>[<\/span>src_name<span>]<\/span><span>,<\/span> model<span>.<\/span>nodes<span>[<\/span>dst_name<span>]<\/span><span>,<\/span> <span>\"igp\"<\/span>\n<\/span><\/span><\/span><span><span><span><span>)<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span>Passing a string yields Ice <span>`ValueError`<\/span> on <span>`getShortestPathRouteRecord`<\/span> and\n<\/span><span>can poison the pooled communicator. Map SDK exceptions to ToolError with the\n<\/span><span>real exception class name; never claim a type error is a missing certificate.\n<\/span><span><span><span>**<\/span><span>Order hops.<\/span><span>**<\/span><\/span> <span>`Route.interfaces`<\/span> is unordered. Reconstruct order by walking\n<\/span><span>from the source node: choose the interface whose node name equals the current\n<\/span><span>node, then advance via <span>`opposite_interface.node`<\/span> (or the other circuit\n<\/span><span>endpoint). Use that walk for IGP fill between node SIDs and from the last\n<\/span><span>anchor to the destination. Emit <span>`resolved_ordered_path`<\/span> as <span>`\"{node}:{iface}\"`<\/span>\n<\/span><span>strings. This topology walk is required; skipping IGP fill because the SDK\n<\/span><span>collection is unordered is incorrect.\n<\/span><span><span><span>**<\/span><span>Circuit IDs.<\/span><span>**<\/span><\/span> Accept unique <span>`node_a`<\/span>+<span>`node_b`<\/span> (reject parallels), SDK\n<\/span><span><span>`ckt{n1|if1|n2|if2}`<\/span>, and shorthand <span>`n1-n2`<\/span> \/ <span>`n2-n1`<\/span>.\n<\/span><span><span><span>**<\/span><span>Simulation Analysis.<\/span><span>**<\/span><\/span>\n<\/span><span><span><span>```<\/span><span>python<\/span>\n<\/span><\/span><span><span><span>sa <span>=<\/span> SimulationAnalysis<span>(<\/span>model<span>,<\/span> failure_types<span>=<\/span>names<span>,<\/span> max_fail_per_int<span>=<\/span>n<span>)<\/span>\n<\/span><\/span><\/span><span><span><span>sa<span>.<\/span>cache_valid <span>=<\/span> <span>True<\/span>\n<\/span><\/span><\/span><span><span><span><span>for<\/span> iface <span>in<\/span> model<span>.<\/span>interfaces<span>:<\/span>\n<\/span><\/span><\/span><span><span><span> rec <span>=<\/span> sa<span>.<\/span>interfaces<span>[<\/span>iface<span>]<\/span> <span># index by model interface<\/span>\n<\/span><\/span><\/span><span><span><span> util <span>=<\/span> rec<span>.<\/span>worst_case_utilization<\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span>Stable <span>`interface_id`<\/span> (<span>`if{node|name}`<\/span> or <span>`{node}:{name}`<\/span>). Never a wrapper\n<\/span><span><span>`repr`<\/span>. Do not iterate SA wrappers as if they were model interfaces.\n<\/span><span><span><span>**<\/span><span>Growth.<\/span><span>**<\/span><\/span> kebab-case GenericTool JSON and DesignAPI filesystem round-trip:\n<\/span><span><span>1.<\/span> Set <span>`demand.growth_percent`<\/span> on active demands.\n<\/span><span><span>2.<\/span> <span>`network.rpc_plan.serializeToFileSystem(\"\/tmp\/mcp_growth_&lt;unique&gt;.txt\")`<\/span>.\n<\/span><span><span>3.<\/span> <span>`pm.newPlanFromFileSystem(server_path)`<\/span>; `newGenericTool().runTool(net,\n<\/span><span> \"create_growth_plans\", json.dumps({\n<\/span><span> \"plan-file\": server_path,\n<\/span><span> \"create-plans-from\": \"DEMANDS\",\n<\/span><span> \"num-periods\": num_periods,\n<\/span><span> \"period-inc\": period_inc,\n<\/span><span> \"growth-method\": \"COMPOUND\",\n<\/span><span> \"run-sim-analysis\": False,\n<\/span><span>}))`.\n<\/span><span><span>4.<\/span> If the tool <span><span>**<\/span><span>raises<\/span><span>**<\/span><\/span> or reports success without changing demand traffic,\n<\/span><span> apply compound <span>`(1 + g\/100) ** (period_inc * num_periods)`<\/span> via\n<\/span><span> <span>`DemandTrafficKey`<\/span> \/ <span>`Demand.traffic`<\/span> and set <span>`fallback_used`<\/span> \/\n<\/span><span> <span>`compound_fallback_applied`<\/span>. Do not fail the tool solely because\n<\/span><span> <span>`create_growth_plans`<\/span> threw when fallback can still produce COMPOUND results.\n<\/span><span><span>5.<\/span> <span>`finally`<\/span>: remove local temps and extra PlanManager keys.\n<\/span><span><span>6.<\/span> SIMPLE: linear <span>`1 + (g\/100)*periods`<\/span> through a verified API, or reject\n<\/span><span> SIMPLE explicitly. Never label compound math as SIMPLE.\n<\/span><span><span><span>##<\/span> MCP tools<\/span>\n<\/span><span>Implement every tool below with detailed docstrings, validated bounded inputs,\n<\/span><span>deterministically ordered outputs, and the common response\/error conventions.\n<\/span><span><span><span>###<\/span> Plan lifecycle and catalogs<\/span>\n<\/span><span><span>1.<\/span> <span>`upload_plan(name, content, encoding=\"text\", plan_id=None, validate=True, confirm=False)`<\/span>\n<\/span><span> <span>-<\/span> stage and register content;\n<\/span><span> <span>-<\/span> optionally open it with OPM before publishing it as ready;\n<\/span><span> <span>-<\/span> remove all state if validation fails;\n<\/span><span> <span>-<\/span> return plan ID, canonical reference, safe metadata, digest, validation\n<\/span><span> state, and model counts;\n<\/span><span> <span>-<\/span> if <span>`validate=False`<\/span>, set state to <span>`unchecked`<\/span>, return null model counts,\n<\/span><span> and require successful validation before any analysis operation.\n<\/span><span><span>2.<\/span> <span>`list_uploaded_plans()`<\/span>.\n<\/span><span><span>3.<\/span> <span>`delete_uploaded_plan(plan_id, confirm=False)`<\/span>.\n<\/span><span><span>4.<\/span> <span>`designapi_health_check(plan_ref=None)`<\/span>.\n<\/span><span><span>5.<\/span> <span>`get_plan_summary(plan_ref=None)`<\/span>.\n<\/span><span><span>6.<\/span> <span>`list_circuits(plan_ref=None, node_filter=None, active_only=True, limit=100)`<\/span>\n<\/span><span> <span>-<\/span> include stable circuit identity, endpoints, interfaces, capacity, and\n<\/span><span> active state;\n<\/span><span> <span>-<\/span> cap the limit and report truncation based on whether additional matches\n<\/span><span> actually exist.\n<\/span><span><span>7.<\/span> <span>`list_demands(plan_ref=None, source=None, destination=None, active_only=True, limit=100)`<\/span>\n<\/span><span> <span>-<\/span> include stable identity\/name, endpoints, service class, traffic with\n<\/span><span> units, and active state;\n<\/span><span> <span>-<\/span> cap the limit and report true truncation.\n<\/span><span><span><span>###<\/span> Route and LSP inspection<\/span>\n<\/span><span><span>8.<\/span> <span>`get_demand_path(source, destination, plan_ref=None, demand_name=None, service_)`<\/span>\n<\/span><span> <span>-<\/span> reset failures and traffic state;\n<\/span><span> <span>-<\/span> recompute baseline routing;\n<\/span><span> <span>-<\/span> reject ambiguous demand matches;\n<\/span><span> <span>-<\/span> return routed state, traffic and units, metric, latency, ECMP percentage,\n<\/span><span> interfaces, traffic shares, and hop count.\n<\/span><span><span>9.<\/span> <span>`get_igp_path(source, destination, plan_ref=None, metric=\"igp\")`<\/span>\n<\/span><span> <span>-<\/span> allow only metrics verified for the installed SDK, such as IGP, BGP, TE,\n<\/span><span> and latency;\n<\/span><span> <span>-<\/span> resolve endpoints with <span>`model.nodes[name]`<\/span> and call <span>`shortest_path`<\/span> with\n<\/span><span> those Node objects;\n<\/span><span> <span>-<\/span> return the SDK-provided path while explicitly stating whether ordering is\n<\/span><span> guaranteed; if unordered, also return a topology-walk ordered list.\n<\/span><span><span>10.<\/span> <span>`list_lsps(plan_ref=None, source=None, destination=None, active_only=True, limit=100)`<\/span>\n<\/span><span> <span>-<\/span> include stable identity, type, setup bandwidth and units, color, state,\n<\/span><span> and path options.\n<\/span><span><span>11.<\/span> <span>`get_lsp_path(source, plan_ref=None, lsp_name=None, destination=None, color=None, destination_ip=None, path_option=None)`<\/span>\n<\/span><span> <span>-<\/span> resolve a named RSVP-TE LSP or an SR policy identified by source,\n<\/span><span> destination, and color;\n<\/span><span> <span>-<\/span> reject incomplete or ambiguous identifiers;\n<\/span><span> <span>-<\/span> support a specific path option;\n<\/span><span> <span>-<\/span> reset simulation state and recompute baseline routing;\n<\/span><span> <span>-<\/span> return routed state, type, endpoints, setup bandwidth, color, metric,\n<\/span><span> latency, and ECMP data;\n<\/span><span> <span>-<\/span> return configured segment-list steps;\n<\/span><span> <span>-<\/span> expand interface SIDs directly;\n<\/span><span> <span>-<\/span> expand node SIDs with SDK-provided IGP shortest-path fill using Node\n<\/span><span> objects plus the topology walk in the operational contract;\n<\/span><span> <span>-<\/span> append IGP fill from the final segment anchor to the destination using\n<\/span><span> the same walk;\n<\/span><span> <span>-<\/span> report unsupported or unresolved segment steps as warnings;\n<\/span><span> <span>-<\/span> clearly distinguish <span>`resolved_ordered_path`<\/span> (<span>`\"{node}:{iface}\"`<\/span> strings),\n<\/span><span> unordered simulation <span>`interface_set`<\/span>, and unconstrained\n<\/span><span> <span>`reference_igp_shortest_path`<\/span> (also Node-object <span>`shortest_path`<\/span>);\n<\/span><span> <span>-<\/span> never fabricate order by iterating a set. The topology walk above is the\n<\/span><span> required reconstruction; do not omit IGP fill because the SDK collection\n<\/span><span> is unordered.\n<\/span><span><span><span>###<\/span> Explicit deterministic failure simulation<\/span>\n<\/span><span><span>12.<\/span> <span>`failure_sim(plan_ref=None, node_a=None, node_b=None, failed_circuits=None, top_n=25, min_traffic_delta_mbps=1.0, sample_reroutes=5)`<\/span>\n<\/span><span> <span>-<\/span> accept exactly one selector form: a non-empty circuit-ID list or a\n<\/span><span> complete node pair;\n<\/span><span> <span>-<\/span> resolve every circuit uniquely before changing simulation state; reject\n<\/span><span> a node pair matching parallel circuits and require explicit circuit IDs;\n<\/span><span> <span>-<\/span> treat <span>`ckt{...}`<\/span>, <span>`nodeA-nodeB`<\/span>, and <span>`nodeB-nodeA`<\/span> as circuit-ID list\n<\/span><span> forms;\n<\/span><span> <span>-<\/span> compute baseline demand paths and per-interface traffic\/utilization;\n<\/span><span> <span>-<\/span> apply exactly the requested circuit failures with\n<\/span><span> <span>`model.route_simulation = [circuits]`<\/span> (not <span>`.failed = True`<\/span>) and recompute;\n<\/span><span> <span>-<\/span> return applied circuit IDs, active-demand count, rerouted and unrouted\n<\/span><span> counts, bounded sample reroutes with before\/after paths, largest bounded\n<\/span><span> interface traffic changes, before\/after utilization, newly\n<\/span><span> oversubscribed interfaces, and baseline oversubscription count;\n<\/span><span> <span>-<\/span> use deterministic tie-breaking and state units;\n<\/span><span> <span>-<\/span> do not mutate the stored baseline.\n<\/span><span>This tool is only for a known deterministic what-if; it is not a worst-case\n<\/span><span>engine.\n<\/span><span><span><span>###<\/span> Worst-case Simulation Analysis<\/span>\n<\/span><span><span>13.<\/span> <span>`get_wc_traffic(plan_ref=None, failure_sets=\"nodes,circuits\", max_fail_per_int=10, limit=15)`<\/span>\n<\/span><span> <span>-<\/span> normalize string or list input;\n<\/span><span> <span>-<\/span> validate failure-set names against an explicit allowlist proven valid for\n<\/span><span> the installed release;\n<\/span><span> <span>-<\/span> invoke Cisco <span>`SimulationAnalysis`<\/span> using its verified API;\n<\/span><span> <span>-<\/span> read worst-case utilization via <span>`sa.interfaces[model_iface]`<\/span> for each\n<\/span><span> model interface; never use a wrapper object's <span>`repr`<\/span> as an id;\n<\/span><span> <span>-<\/span> rank interfaces by vendor-produced worst-case utilization with stable\n<\/span><span> tie-breaking;\n<\/span><span> <span>-<\/span> return failure sets, units, bounded top interfaces, relevant vendor\n<\/span><span> warnings, and scenario count only if the verified API exposes it; never\n<\/span><span> infer a count by guessed enumeration.\n<\/span><span>Never implement this by repeatedly calling <span>`failure_sim`<\/span>, enumerating guessed\n<\/span><span>failures, or relabeling ordinary utilization as worst-case output. If the\n<\/span><span>installed SDK does not expose the required API, apply the prerequisite gate and\n<\/span><span>do not generate the server. If the verified API exists but is unlicensed or\n<\/span><span>unavailable in the deployed service, return a runtime capability error.\n<\/span><span><span><span>###<\/span> Traffic growth<\/span>\n<\/span><span><span>14.<\/span> <span>`get_traffic_growth(plan_ref=None, growth_percent=33.0, num_periods=1, period_inc=1, growth_method=\"COMPOUND\", threshold=100.0, near_capacity=80.0)`<\/span>\n<\/span><span> <span>-<\/span> validate finite numeric values and sensible bounded ranges;\n<\/span><span> <span>-<\/span> require <span>`0 &lt;= near_capacity &lt;= threshold`<\/span>;\n<\/span><span> <span>-<\/span> collect baseline per-circuit utilization, checking both sides and\n<\/span><span> reporting the bottleneck side;\n<\/span><span> <span>-<\/span> set growth attributes required by the installed SDK;\n<\/span><span> <span>-<\/span> use the verified Design plan-manager transfer lifecycle to create a\n<\/span><span> uniquely named server-side working plan from the uploaded artifact,\n<\/span><span> retain its returned key rather than guessing a server path, and retrieve\n<\/span><span> the generated result; if the release supports only a documented\n<\/span><span> server-visible filesystem path, validate that deployment prerequisite\n<\/span><span> explicitly;\n<\/span><span> <span>-<\/span> invoke <span>`create_growth_plans`<\/span> through Design RPC using the kebab-case\n<\/span><span> options and <span>`serializeToFileSystem`<\/span> \/ <span>`newPlanFromFileSystem`<\/span> lifecycle\n<\/span><span> in the operational contract (not camelCase, not <span>`newPlanFromBytes`<\/span> alone);\n<\/span><span> <span>-<\/span> inspect the generated plan and verify whether expected demand growth was\n<\/span><span> actually applied;\n<\/span><span> <span>-<\/span> if the tool raises, returns no new plan key, or reports success without\n<\/span><span> applying COMPOUND growth, a compatibility adapter must apply the\n<\/span><span> mathematically correct compound multiplier through the documented\n<\/span><span> demand-traffic RPC API and report that fallback;\n<\/span><span> <span>-<\/span> for SIMPLE growth, implement the documented mathematics through a\n<\/span><span> verified API or reject it explicitly; never use compound arithmetic while\n<\/span><span> labeling it SIMPLE;\n<\/span><span> <span>-<\/span> create local temporary files with mode <span>`0600`<\/span> inside the private staging\n<\/span><span> filesystem, serialize and reopen through OPM, and clean both local and\n<\/span><span> server-side temporary plans by their verified keys in <span>`finally`<\/span> blocks on\n<\/span><span> success or exception; after a soft timeout, cleanup must occur when the\n<\/span><span> underlying call actually returns rather than pretending it was cancelled;\n<\/span><span> <span>-<\/span> return multiplier, method, periods, whether tool growth or a compatibility\n<\/span><span> fallback was applied, baseline utilization summary, after-growth\n<\/span><span> oversubscriptions, and near-capacity circuits.\n<\/span><span><span><span>##<\/span> Simulation correctness<\/span>\n<\/span><span><span>-<\/span> Reset failure, route, and traffic state before every baseline with\n<\/span><span> <span>`model.route_simulation = []`<\/span> (never <span>`None`<\/span>).\n<\/span><span><span>-<\/span> Recompute after applying failures via <span>`model.route_simulation = [circuits]`<\/span>.\n<\/span><span><span>-<\/span> Include only active objects where requested.\n<\/span><span><span>-<\/span> Handle missing traffic, utilization, capacity, latency, and route values\n<\/span><span> safely without converting \u201cunknown\u201d to zero unless that meaning is documented.\n<\/span><span><span>-<\/span> Compare both circuit sides and report the actual bottleneck side.\n<\/span><span><span>-<\/span> Sort every ranked or sampled result deterministically with a stable secondary\n<\/span><span> key.\n<\/span><span><span>-<\/span> Never claim an unordered collection is a hop sequence.\n<\/span><span><span>-<\/span> Keep all mutable simulation state local to one opened plan context.\n<\/span><span><span>-<\/span> Preserve and verify units in field names or adjacent metadata.\n<\/span><span><span>-<\/span> Apply explicit hard caps to circuit IDs, catalog rows, path interfaces,\n<\/span><span> segment steps, reroutes, utilization rows, warnings, audit-field lengths, and\n<\/span><span> total serialized response size; include truncation metadata.\n<\/span><span><span><span>##<\/span> Certificate and cryptographic requirements<\/span>\n<\/span><span>Never disable TLS verification or use custom cryptography, MD5, SHA-1, DES,\n<\/span><span>3DES, RC4, Blowfish, AES-CBC, or AES-ECB. Use SHA-256 only for non-secret\n<\/span><span>content digests; token authentication must use a documented verifier and\n<\/span><span>constant-time comparison where applicable.\n<\/span><span>Do not embed or return certificate or private-key content. When expected X.509\n<\/span><span>certificate files are configured or discovered, flag them for operator\n<\/span><span>verification and document this command:\n<\/span><span><span><span>```<\/span><span>bash<\/span>\n<\/span><\/span><span><span><span><span>env<\/span> <span>-u<\/span> LD_LIBRARY_PATH openssl x509 <span>-text<\/span> <span>-noout<\/span> <span>-in<\/span> <span>&lt;<\/span>certificate_file<span>&gt;<\/span><\/span>\n<\/span><\/span><span><span><span>```<\/span><\/span>\n<\/span><span>If <span>`LD_LIBRARY_PATH`<\/span> includes the SDK <span>`lib`<\/span> directory, the system openssl CLI\n<\/span><span>fails (<span>`undefined symbol: EVP_sm3`<\/span>) unless you unset that variable.\n<\/span><span>The operator must verify:\n<\/span><span><span>-<\/span> <span>`notBefore`<\/span> and <span>`notAfter`<\/span> validity;\n<\/span><span><span>-<\/span> RSA keys are at least 2048 bits or EC keys use P-256 or stronger;\n<\/span><span><span>-<\/span> signatures use SHA-2 or stronger, never MD5 or SHA-1;\n<\/span><span><span>-<\/span> whether issuer and subject indicate a self-signed certificate and, if so,\n<\/span><span> that its use is intentional and limited to private development\/testing or an\n<\/span><span> explicitly managed internal trust domain.\n<\/span><span>When configured certificate files are present, parse the public certificates\n<\/span><span>with a maintained library or documented standard-library facility and fail\n<\/span><span>closed on expiry\/not-yet-valid status, RSA keys below 2048 bits, EC curves below\n<\/span><span>P-256, or MD5\/SHA-1 signatures. Report self-signed status as an operator warning\n<\/span><span>and never expose certificate contents. <span>`MCP_TOKEN_FILE`<\/span> and other application\n<\/span><span>secret files must be mode <span>`0600`<\/span>\/<span>`0400`<\/span> (fail closed). IceSSL private keys under\n<\/span><span><span>`CARIDEN_HOME\/etc\/certs`<\/span>: if the file exists and is owner-readable, log a\n<\/span><span>warning to stderr when the mode is not <span>`0600`<\/span>\/<span>`0400`<\/span>, but do <span><span>**<\/span><span>not<\/span><span>**<\/span><\/span> refuse\n<\/span><span>startup solely because a lab key is group-readable (<span>`0664`<\/span>). Still fail closed\n<\/span><span>if the IceSSL key is missing, unreadable by the process, or world-writable.\n<\/span><span>Do not read or log key content outside the TLS\/SDK facility that consumes it.\n<\/span><span><span><span>##<\/span> Required validation before completion<\/span>\n<\/span><span>If the prerequisite gate failed, stop here and return only the `status:\n<\/span><span>blocked` evidence report; the file-specific checks below do not apply.\n<\/span><span>If the prerequisite gate passed, before presenting the generated file classify\n<\/span><span>checks as (a) mandatory offline,\n<\/span><span>(b) SDK-present offline, (c) live DesignAPI\/licensing, or (d) deployment\n<\/span><span>operator checks. All applicable offline checks must pass. Run live\/deployment\n<\/span><span>checks only when suitable services, identities, fixtures, and certificate files\n<\/span><span>are actually available; report the others as <span>`not run`<\/span>, never as passed.\n<\/span><span>Then:\n<\/span><span><span>1.<\/span> Compile the file with the target Python interpreter.\n<\/span><span><span>2.<\/span> Run Ruff or an equivalent static check if available.\n<\/span><span><span>3.<\/span> Exercise a no-network <span>`--check`<\/span> path.\n<\/span><span><span>4.<\/span> Enumerate registrations and confirm all 5 exact resource URI templates, 2\n<\/span><span> prompts, and 14 tools above are registered.\n<\/span><span><span>5.<\/span> Confirm all referenced functions, imports, and compatibility adapters exist.\n<\/span><span><span>6.<\/span> Confirm stdio mode writes no banner or logs to stdout.\n<\/span><span><span>7.<\/span> Search the file for embedded secrets, private keys, certificates, tokens,\n<\/span><span> deployment addresses, credentialed URLs, weak cryptography, and disabled TLS\n<\/span><span> verification.\n<\/span><span><span>8.<\/span> Test traversal, symlink, oversized content, malformed base64, ID collision,\n<\/span><span> failed validation cleanup, unauthorized cross-owner lookup, and deletion\n<\/span><span> confirmation.\n<\/span><span><span>9.<\/span> Test concurrent registry operations and analysis-versus-delete behavior.\n<\/span><span><span>10.<\/span> Confirm direct plan paths cannot escape allowed roots and cannot be used by\n<\/span><span> HTTP principals.\n<\/span><span><span>11.<\/span> Confirm worst-case analysis calls the verified Cisco\n<\/span><span> <span>`SimulationAnalysis`<\/span>, never <span>`failure_sim`<\/span>.\n<\/span><span><span>12.<\/span> Confirm SIMPLE growth is correct or explicitly rejected.\n<\/span><span><span>13.<\/span> Confirm temporary files and Design-side temporary plans are cleaned up when\n<\/span><span> each intermediate operation fails.\n<\/span><span><span>14.<\/span> Confirm HTTP cannot start without valid authentication and cannot bind\n<\/span><span> non-loopback without explicit authorization.\n<\/span><span><span>15.<\/span> Confirm one authenticated principal cannot infer, read, operate on, or\n<\/span><span> delete another principal's plans.\n<\/span><span><span>16.<\/span> Confirm <span>`os.execv`<\/span> (or equivalent) is used so <span>`python3 cp_sample_mcp.py`<\/span>\n<\/span><span> works without a pre-exported <span>`LD_LIBRARY_PATH`<\/span>.\n<\/span><span><span>17.<\/span> If <span>`CW_DESIGNAPI_HOST`<\/span> is set and <span>`us_wan.txt`<\/span> is available, run live\n<\/span><span> DesignAPI checks and fix failures: upload (33\/50\/95); demand path\n<\/span><span> er1.sjc\u2192er1.mia hop_count 6 metric <span><span>~<\/span><span>2169; IGP cr1.sjc\u2192cr1.kcy with Node\n<\/span><\/span><\/span><span><span><span> objects and no Ice ValueError; <span>`failure_sim`<\/span> node pair rerouted_count 10;\n<\/span><\/span><\/span><span><span><span> <span>`failure_sim`<\/span> <span>`failed_circuits=[\"cr1.sjc-cr1.kcy\"]`<\/span>; <span>`get_wc_traffic`<\/span>\n<\/span><\/span><\/span><span><span><span> circuits with <\/span><span>~<\/span><\/span>113% and human-readable interface ids; <span>`get_traffic_growth`<\/span>\n<\/span><span> 33% one period with <span>`ok`<\/span> true and either tool growth or fallback.\n<\/span><span>Fix all failures before answering. Do not claim checks that could not run.\n<\/span><span>End with a concise report containing:\n<\/span><span><span>-<\/span> the file created;\n<\/span><span><span>-<\/span> compile\/static-check results;\n<\/span><span><span>-<\/span> detected Python, FastMCP, and Cisco SDK versions;\n<\/span><span><span>-<\/span> the evidence source used for each version-sensitive Cisco capability;\n<\/span><span><span>-<\/span> tests actually run;\n<\/span><span><span>-<\/span> any remaining deployment-only checks, especially live DesignAPI connectivity,\n<\/span><span> licensing, and operator certificate verification.\n<\/span><\/code><\/pre>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Crosswork Planning predicts the impact of network changes, traffic growth, new services, and potential failures. Operators usually perform these tasks in the Design\u2026<\/p>\n","protected":false},"author":1,"featured_media":187,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[39,72,111,106,113,69,109,115,117],"tags":[],"class_list":["post-183","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-en","category-ai-fa-en","category-automation-en","category-crosswork-en","category-crosswork-planning-en","category-iosxr-en-en","category-mcp-en","category-python-en","category-python-libraries-en"],"_links":{"self":[{"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/posts\/183","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/comments?post=183"}],"version-history":[{"count":1,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/posts\/183\/revisions"}],"predecessor-version":[{"id":185,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/posts\/183\/revisions\/185"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/media\/187"}],"wp:attachment":[{"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/media?parent=183"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/categories?post=183"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/tags?post=183"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}