Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions mcp-servers-on-oci-container-instances/deploy/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

In this lab, you create an OCI Resource Manager stack from the workshop package
and run the apply job. Resource Manager creates the OCI networking, API Gateway,
Container Instance, and three MCP server containers.
Container Instance, and three MCP server containers. The Container Instance runs
in a private subnet and uses a NAT Gateway for outbound public image pulls.

Estimated Time: 15 minutes

Expand All @@ -22,13 +23,13 @@ In this lab, you will:

Complete the workshop introduction and the Get Started lab. Make sure you can
access an OCI tenancy and a compartment where you can create Resource Manager,
networking, API Gateway, and Container Instance resources.
networking, NAT Gateway, API Gateway, and Container Instance resources.

## Task 1: Launch the Resource Manager stack

1. Select **Deploy to Oracle Cloud**.

[![Deploy to Oracle Cloud](../images/deploy-to-oracle-cloud-centered.svg)](https://cloud.oracle.com/resourcemanager/stacks/create?zipUrl=https://github.com/Phirlly/developer/raw/main/mcp-servers-on-oci-container-instances/files/resource-manager/mcp-servers-on-oci-container-instances-rm.zip)
[![Deploy to Oracle Cloud](../images/deploy-to-oracle-cloud-centered.svg)](https://cloud.oracle.com/resourcemanager/stacks/create?zipUrl=https://github.com/oracle-livelabs/developer/raw/main/mcp-servers-on-oci-container-instances/files/resource-manager/mcp-servers-on-oci-container-instances-rm.zip)

![Deploy to Oracle Cloud button](../images/01-create-stack-package.png)

Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ resource "oci_container_instances_container_instance" "mcp_servers" {

vnics {
display_name = "${local.name_prefix}-vnic"
is_public_ip_assigned = true
is_public_ip_assigned = false
subnet_id = oci_core_subnet.container_instance.id
}

Expand Down
24 changes: 22 additions & 2 deletions mcp-servers-on-oci-container-instances/files/terraform/network.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ resource "oci_core_internet_gateway" "mcp_lab" {
vcn_id = oci_core_vcn.mcp_lab.id
}

resource "oci_core_nat_gateway" "mcp_lab" {
compartment_id = var.compartment_ocid
display_name = "${local.name_prefix}-nat"
freeform_tags = local.common_freeform_tags
vcn_id = oci_core_vcn.mcp_lab.id
}

resource "oci_core_route_table" "mcp_lab" {
compartment_id = var.compartment_ocid
display_name = "${local.name_prefix}-rt"
Expand All @@ -27,6 +34,19 @@ resource "oci_core_route_table" "mcp_lab" {
}
}

resource "oci_core_route_table" "container_instance" {
compartment_id = var.compartment_ocid
display_name = "${local.name_prefix}-container-instance-rt"
freeform_tags = local.common_freeform_tags
vcn_id = oci_core_vcn.mcp_lab.id

route_rules {
destination = "0.0.0.0/0"
destination_type = "CIDR_BLOCK"
network_entity_id = oci_core_nat_gateway.mcp_lab.id
}
}

resource "oci_core_security_list" "api_gateway" {
compartment_id = var.compartment_ocid
display_name = "${local.name_prefix}-api-gateway-sl"
Expand Down Expand Up @@ -113,8 +133,8 @@ resource "oci_core_subnet" "container_instance" {
display_name = "${local.name_prefix}-container-instance-subnet"
dns_label = "mcpservers"
freeform_tags = local.common_freeform_tags
prohibit_public_ip_on_vnic = false
route_table_id = oci_core_route_table.mcp_lab.id
prohibit_public_ip_on_vnic = true
route_table_id = oci_core_route_table.container_instance.id
security_list_ids = [oci_core_security_list.container_instance.id]
vcn_id = oci_core_vcn.mcp_lab.id
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ In this workshop, you will:

The Resource Manager stack creates:

* one VCN for the lab;
* one VCN for the lab, with a public API Gateway subnet and a private Container
Instance subnet;
* one NAT Gateway for outbound access from the private Container Instance
subnet;
* one OCI API Gateway HTTPS endpoint;
* one OCI Container Instance;
* three containers in that Container Instance:
Expand All @@ -50,7 +53,8 @@ After deployment, Resource Manager returns three MCP URLs:
You need:

* access to an OCI tenancy and a compartment where you can create Resource
Manager stacks, networking, API Gateway, and Container Instance resources;
Manager stacks, networking, NAT Gateway, API Gateway, and Container Instance
resources;
* an MCP-capable AI client for the client-connection labs;
* a least-privilege GitHub token available for the GitHub MCP lab.

Expand Down
2 changes: 1 addition & 1 deletion mcp-servers-on-oci-container-instances/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Use the step-by-step guide:
## Deploy to OCI

<p align="center">
<a href="https://cloud.oracle.com/resourcemanager/stacks/create?zipUrl=https://github.com/Phirlly/developer/raw/main/mcp-servers-on-oci-container-instances/files/resource-manager/mcp-servers-on-oci-container-instances-rm.zip">
<a href="https://cloud.oracle.com/resourcemanager/stacks/create?zipUrl=https://github.com/oracle-livelabs/developer/raw/main/mcp-servers-on-oci-container-instances/files/resource-manager/mcp-servers-on-oci-container-instances-rm.zip">
<img src="https://oci-resourcemanager-plugin.plugins.oci.oraclecloud.com/latest/deploy-to-oracle-cloud.svg" alt="Deploy to Oracle Cloud">
</a>
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ Complete Lab 1 and start the Resource Manager apply job for this workshop.

## Task 3: Review the created resources

1. Open the job resources and confirm Resource Manager created the expected API
Gateway, networking, and Container Instance resources.
1. Open the job resources and confirm Resource Manager created the expected
networking, NAT Gateway, API Gateway, and Container Instance resources.

![Resource Manager job resources](../images/10-job-resources.png)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ def extract_named_blocks(text: str, block_name: str) -> list[str]:
return blocks


def extract_labeled_block(text: str, header: str) -> str | None:
header_index = text.find(header)
if header_index == -1:
return None

open_index = text.find("{", header_index)
if open_index == -1:
return None

depth = 0
for index in range(open_index, len(text)):
if text[index] == "{":
depth += 1
elif text[index] == "}":
depth -= 1
if depth == 0:
return text[header_index : index + 1]

return None


def validate_versions(texts: dict[str, str], failures: list[str]) -> None:
text = texts.get("versions.tf", "")
require_contains(failures, "versions.tf", text, 'source = "oracle/oci"')
Expand Down Expand Up @@ -271,12 +292,16 @@ def validate_network(texts: dict[str, str], failures: list[str]) -> None:
"oci_core_vcn",
"oci_core_subnet",
"oci_core_internet_gateway",
"oci_core_nat_gateway",
"oci_core_route_table",
"oci_core_security_list",
]:
require_contains(failures, "network.tf", text, f'resource "{resource_type}"')

for resource in [
'resource "oci_core_nat_gateway" "mcp_lab"',
'resource "oci_core_route_table" "mcp_lab"',
'resource "oci_core_route_table" "container_instance"',
'resource "oci_core_security_list" "api_gateway"',
'resource "oci_core_security_list" "container_instance"',
'resource "oci_core_subnet" "api_gateway"',
Expand All @@ -295,7 +320,56 @@ def validate_network(texts: dict[str, str], failures: list[str]) -> None:
require_contains(failures, "network.tf", text, "var.terraform_mcp_port")
require_contains(failures, "network.tf", text, "var.github_mcp_port")
require_contains(failures, "network.tf", text, "var.playwright_mcp_port")
require_contains(failures, "network.tf", text, "prohibit_public_ip_on_vnic = false")
require_contains(failures, "network.tf", text, "network_entity_id = oci_core_internet_gateway.mcp_lab.id")
require_contains(failures, "network.tf", text, "network_entity_id = oci_core_nat_gateway.mcp_lab.id")
require_contains(failures, "network.tf", text, "route_table_id = oci_core_route_table.mcp_lab.id")
require_contains(
failures,
"network.tf",
text,
"route_table_id = oci_core_route_table.container_instance.id",
)
api_gateway_subnet = extract_labeled_block(
text, 'resource "oci_core_subnet" "api_gateway"'
)
if api_gateway_subnet is None:
failures.append("network.tf must define the API Gateway subnet resource")
else:
if "prohibit_public_ip_on_vnic = false" not in api_gateway_subnet:
failures.append("network.tf must keep the API Gateway subnet public")
if "route_table_id = oci_core_route_table.mcp_lab.id" not in api_gateway_subnet:
failures.append(
"network.tf must route the API Gateway subnet through the internet gateway route table"
)

container_instance_subnet = extract_labeled_block(
text, 'resource "oci_core_subnet" "container_instance"'
)
if container_instance_subnet is None:
failures.append("network.tf must define the Container Instance subnet resource")
else:
if "prohibit_public_ip_on_vnic = true" not in container_instance_subnet:
failures.append("network.tf must make the Container Instance subnet private")
if (
"route_table_id = oci_core_route_table.container_instance.id"
not in container_instance_subnet
):
failures.append(
"network.tf must route the Container Instance subnet through the NAT route table"
)

container_instance_route_table = extract_labeled_block(
text, 'resource "oci_core_route_table" "container_instance"'
)
if container_instance_route_table is None:
failures.append("network.tf must define the Container Instance route table")
elif (
"network_entity_id = oci_core_nat_gateway.mcp_lab.id"
not in container_instance_route_table
):
failures.append(
"network.tf must route private Container Instance subnet egress through NAT Gateway"
)

ingress_blocks = extract_named_blocks(text, "ingress_security_rules")
for port_variable in [
Expand Down Expand Up @@ -335,7 +409,7 @@ def validate_container_instance(texts: dict[str, str], failures: list[str]) -> N
require_contains(failures, "container-instance.tf", text, 'data "oci_core_vnic"')
require_contains(failures, "container-instance.tf", text, "available_container_shape_names")
require_contains(failures, "container-instance.tf", text, "precondition")
require_contains(failures, "container-instance.tf", text, "is_public_ip_assigned = true")
require_contains(failures, "container-instance.tf", text, "is_public_ip_assigned = false")
require_contains(failures, "container-instance.tf", text, "subnet_id = oci_core_subnet.container_instance.id")
require_contains(failures, "container-instance.tf", text, "var.container_ocpus <= 64")
require_contains(failures, "container-instance.tf", text, "var.container_ocpus <= 94")
Expand Down Expand Up @@ -383,6 +457,9 @@ def validate_container_instance(texts: dict[str, str], failures: list[str]) -> N
f"container-instance.tf must not configure secret environment name {unsafe_name}"
)

if "is_public_ip_assigned = true" in text:
failures.append("container-instance.tf must not assign a public IP to the Container Instance")


def validate_api_gateway(texts: dict[str, str], failures: list[str]) -> None:
text = texts.get("api-gateway.tf", "")
Expand Down