Skip to content

Commit 03dcb3e

Browse files
authored
@W-22137880 add protect mcp skill (#23)
* Fix double scroll * Add MCP asset type * Add protect-mcp-server-with-policies skill and fix duplicate YAML key New 9-step JTBD skill for protecting MCP servers with policies on Flex Gateway. Searches Exchange with types=mcp (no org filter) and includes a list-instances step for the apply-policy-only path. Also fixes a duplicate environmentId key in the deploy step YAML block of both skills, which caused ruamel.yaml to silently drop the block and render the step as prose-only without the interactive panel. * Add apply-policy-to-mcp-instance skill Simplified 4-step skill for applying a policy to an existing MCP server instance in API Manager. Assumes the user is already authenticated. Steps: list environments, list instances, browse policy templates, apply. * Revert "Add apply-policy-to-mcp-instance skill" This reverts commit 42e5f1e. * Add family query parameter to API Manager and use it in MCP skill Add undocumented `family` enum parameter (api, agentic, llm) to the listOrganizationsEnvironmentsApis operation in the API Manager spec. Use `family=agentic` as a hardcoded filter in protect-mcp-server Step 7 to list only MCP server instances instead of all API instances. * Hide auth panel for prose-only skill pages Wrap auth-panel-right and auth modal in prose_only conditional so prose-only skills (like Anypoint Platform Operations) show the header bar with logo and title but not the authentication controls. Add prose-only skill fixture and 6 smoke tests verifying the header bar is present, auth panel right is absent, no auth modal, no interactive mode toggle, and Guide badge is shown. * Hide step count badge for prose-only skills on homepage and sidebar Prose-only skills (0 steps) no longer show "0 steps" on homepage cards or API detail page sidebar. The badge is simply omitted.
1 parent df61cda commit 03dcb3e

10 files changed

Lines changed: 669 additions & 11 deletions

File tree

apis/api-manager/api.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ paths:
105105
allowReserved: true
106106
schema:
107107
type: string
108+
- name: family
109+
in: query
110+
description: Filter API instances by family type. Use "agentic" to list MCP server instances, "llm" for LLM-based APIs, or "api" for standard APIs.
111+
schema:
112+
type: string
113+
enum:
114+
- api
115+
- agentic
116+
- llm
108117
- name: filters
109118
in: query
110119
allowReserved: true

apis/exchange-experience/api.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ paths:
7272
- template
7373
- example
7474
- custom
75+
- mcp
7576
- name: rootOrganizationId
7677
description: Root organization ID to scope the asset search
7778
in: query

scripts/portal_generator/assets/styles.css

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6330,7 +6330,6 @@ details[open] .examples-toggle-icon {
63306330
flex-direction: column;
63316331
flex: 1;
63326332
min-height: 200px;
6333-
/* Ensure content area has minimum height */
63346333
}
63356334

63366335
.try-response-body,
@@ -6343,14 +6342,12 @@ details[open] .examples-toggle-icon {
63436342
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
63446343
font-size: var(--font-size-3);
63456344
color: var(--color-text-primary);
6346-
overflow-x: auto;
6347-
overflow-y: auto;
6345+
overflow: hidden;
63486346
margin: 0;
63496347
white-space: pre-wrap;
63506348
word-break: break-word;
63516349
flex: 1;
63526350
min-height: 200px;
6353-
/* Ensure ACE editor has minimum height */
63546351
}
63556352

63566353
.try-response-body.active,

scripts/portal_generator/templates/homepage/skill_card.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ <h3 class="catalog-card-title">{{ skill_name }}</h3>
1111
<div class="catalog-card-footer">
1212
<span class="card-version-badge card-version-list">Skill</span>
1313
<span class="badge badge-skills">Skill</span>
14-
<span class="badge badge-count">{{ skill.step_count|default(0) }} step{{ 's' if skill.step_count != 1 else '' }}</span>
14+
{% if skill.step_count|default(0) > 0 %}
15+
<span class="badge badge-count">{{ skill.step_count }} step{{ 's' if skill.step_count != 1 else '' }}</span>
16+
{% endif %}
1517
</div>
1618
</article>
1719
</a>

scripts/portal_generator/templates/partials/auth_panel.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ <h1 class="api-title"><a href="#overview" class="api-title-link">{{ api_name }}<
1818
</div>
1919
{% endif %}
2020
</div>
21+
{% if not prose_only|default(false) %}
2122
<div class="auth-panel-right">
2223
<button class="auth-panel-status" onclick="openAuthModal()" aria-label="Open authentication modal">
2324
<span class="auth-status-container" id="authStatusContainer">
@@ -38,8 +39,10 @@ <h1 class="api-title"><a href="#overview" class="api-title-link">{{ api_name }}<
3839
</span>
3940
</button>
4041
</div>
42+
{% endif %}
4143
</div>
4244

45+
{% if not prose_only|default(false) %}
4346
<div class="auth-modal" id="authModal" style="display:none" role="dialog" aria-modal="true" aria-labelledby="authModalTitle">
4447
<div class="auth-modal-overlay" onclick="closeAuthModal()"></div>
4548
<div class="auth-modal-content">
@@ -103,3 +106,4 @@ <h4>Context Variables</h4>
103106
</div>
104107
</div>
105108
</div>
109+
{% endif %}

scripts/portal_generator/templates/partials/sidebar.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@
9191
<li>
9292
<a href="../skills/{{ skill.slug }}.html" class="nav-link nav-operation-flat">
9393
<span class="op-name">{{ skill_name }}</span>
94+
{% if step_count > 0 %}
9495
<span class="group-count">({{ step_count }} steps)</span>
96+
{% endif %}
9597
</a>
9698
</li>
9799
{% endfor %}

scripts/tests/conftest.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,22 @@ def sample_api_data_with_skills(sample_api_data):
284284
})
285285

286286

287+
PROSE_ONLY_SKILL_MD = textwrap.dedent("""\
288+
---
289+
name: platform-guide
290+
description: Navigate the Anypoint Platform developer portal.
291+
---
292+
## Overview
293+
This is a prose-only guide with no steps.
294+
295+
## Tips and Best Practices
296+
Start with registry.json for programmatic discovery.
297+
298+
## Troubleshooting
299+
Check network connectivity if portal files are not accessible.
300+
""")
301+
302+
287303
def setup_schema_docs(repo_root: Path):
288304
"""Create minimal schema doc files under a repo root for testing."""
289305
schemas_dir = repo_root / 'docs' / 'schemas'

scripts/tests/test_smoke.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from portal_generator import PortalGenerator
1010
from tests.conftest import (
1111
MINIMAL_OAS_YAML, MINIMAL_EXCHANGE_JSON, MINIMAL_SKILL_MD,
12-
PRIVATE_EXCHANGE_JSON, setup_schema_docs,
12+
PRIVATE_EXCHANGE_JSON, PROSE_ONLY_SKILL_MD, setup_schema_docs,
1313
)
1414

1515

@@ -32,6 +32,10 @@ def generated_portal(tmp_path):
3232
skill_dir.mkdir(parents=True)
3333
(skill_dir / 'SKILL.md').write_text(MINIMAL_SKILL_MD)
3434

35+
prose_skill_dir = repo / 'skills' / 'platform-guide'
36+
prose_skill_dir.mkdir(parents=True)
37+
(prose_skill_dir / 'SKILL.md').write_text(PROSE_ONLY_SKILL_MD)
38+
3539
setup_schema_docs(repo)
3640

3741
output = tmp_path / 'portal_output'
@@ -141,6 +145,37 @@ def test_has_api_link_prefix(self):
141145
assert "__API_LINK_PREFIX__" in script_text
142146

143147

148+
class TestProseOnlySkillPage:
149+
"""Prose-only skills render the header but hide auth panel and interactive elements."""
150+
@pytest.fixture(autouse=True)
151+
def _parse_prose_skill_page(self, generated_portal):
152+
html = (generated_portal / 'skills' / 'platform-guide.html').read_text(encoding='utf-8')
153+
self.soup = BeautifulSoup(html, 'html.parser')
154+
155+
def test_prose_skill_page_exists(self, generated_portal):
156+
assert (generated_portal / 'skills' / 'platform-guide.html').exists()
157+
158+
def test_has_header_bar(self):
159+
header = self.soup.find('div', class_='auth-panel-header-bar')
160+
assert header is not None
161+
162+
def test_no_auth_panel_right(self):
163+
right = self.soup.find('div', class_='auth-panel-right')
164+
assert right is None
165+
166+
def test_no_auth_modal(self):
167+
modal = self.soup.find('div', class_='auth-modal')
168+
assert modal is None
169+
170+
def test_no_interactive_mode_toggle(self):
171+
toggle = self.soup.find('div', class_='skill-mode-toggle-container')
172+
assert toggle is None
173+
174+
def test_has_guide_badge(self):
175+
badge = self.soup.find('span', class_='badge-version', string='Guide')
176+
assert badge is not None
177+
178+
144179
class TestHomepageSkillLinks:
145180
@pytest.fixture(autouse=True)
146181
def _parse_homepage(self, generated_portal):

skills/protect-api-with-policies/SKILL.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ inputs:
253253
environmentId:
254254
from:
255255
variable: environmentId
256-
description: Environment ID from Step 2
256+
description: Environment ID from Step 2 (used in both the URL path and the request body for HY deployment type)
257257
environmentApiId:
258258
from:
259259
variable: environmentApiId
@@ -272,10 +272,6 @@ inputs:
272272
gatewayVersion:
273273
value: "1.0.0"
274274
description: "Gateway version for deployment. Use \"1.0.0\" as the default."
275-
environmentId:
276-
from:
277-
variable: environmentId
278-
description: Environment ID (required in the deployment body for HY type)
279275
overwrite:
280276
value: false
281277
description: "Whether to overwrite an existing deployment"

0 commit comments

Comments
 (0)