Build an MCP server scoped to one agent task
An agent connected to the Port MCP server sees every tool it offers, and any tool it can see, it can call. An agent that only investigates incidents on one service still gets tools for every service in the catalog, and tools that can change production.
A virtual MCP server exposes only the tools you choose, under its own URL. For each tool, you control:
- Name and description, so the agent picks the right tool for the task.
- Arguments, which you can fix to one value, limit to a set of allowed values, rename, or describe.
- Scope, such as which catalog records a query tool can reach.
The server applies these rules on every call, so they hold however the agent is prompted. In this guide, we will build a virtual MCP server for one job: investigating incidents on a checkout service, using Port's own tools.
Common use cases
- Scope an agent to one domain of Port. Expose only the tools for querying the catalog, building it, or managing governance, so each agent loads the tools for its own domain.
- Rename tools in your organization's vocabulary. Give generic tools, such as
list_entities, a name and description that say when to use them. - Fix the values that matter. A tool fixed to one blueprint, one service, or one action never reaches anything else.
- Limit arguments to approved values. Allow only permitted environments, regions, or severity levels. The server rejects any other value.
- Publish one URL per team or job. A platform engineer configures the server once, and developers paste its URL into their IDE.
- Set guidance once for every agent. Server instructions reach each agent when it connects, instead of being copied into every system prompt.
- Keep least privilege. Every call runs with the caller's own permissions, so a virtual MCP server can only narrow what an agent sees. It never grants access.
This guide builds a server for an incident triage agent. It renames Port tools in the job's vocabulary, keeps catalog queries on one service, and lets the agent restart that service only outside production.
Prerequisites
- Virtual MCP servers enabled for your organization. This feature is currently in closed beta. Contact Port's support team to request access.
- A self-service action that restarts a workload, such as
restart_servicein the example below. - An MCP client to test with, such as your IDE or a Port AI agent.
Example scenario
The steps use the following identifiers. Replace them with the ones in your organization.
| Item | Identifier in this guide |
|---|---|
| Service the agent investigates | checkout (a service entity with relations to the services it depends on and the services that call it) |
| Self-service action the agent may run | restart_service, with a workload input |
| Workloads | checkout-test, checkout-staging, and checkout-production |
The goal: the agent can read anything about checkout and the services around it, and can restart checkout in test and staging. It cannot see other services or change production.
Create the server
We start with passthrough tools only: the tools the job needs, exposed unchanged. We leave out every tool that creates, updates, or deletes anything. The tools that change things come back later, in a scoped form.
- Go to the Virtual MCPs tab of the MCPs page.
- Click + Virtual MCP Server.
- Turn on JSON Mode.
- Paste the following JSON, then click Register:
{
"identifier": "checkout-incident-triage",
"title": "Checkout incident triage",
"properties": {
"description": "Tools for investigating incidents on the checkout service.",
"instructions": "You help on-call engineers investigate incidents on the checkout service. Read data before you suggest a change.",
"passthrough_tools": [
"list_blueprints",
"list_entities",
"list_self_service_triggers"
]
}
}
Instructions reach the agent when it connects, next to the tool list. Use them for guidance that applies to every agent using the server. They guide the agent but do not enforce anything. The argument rules in the next sections do.
Copy the server's URL from its URL property, then connect your agent to it instead of the Port MCP server, as described in connect a virtual MCP server.
Rename and describe tools for the job
Agents choose tools by name and description. Generic names, such as list_entities, make the agent guess which tool answers its question. In this section, we rename the tools in the vocabulary of the job, without changing what they can reach.
Write each name and description for an agent that is picking a tool for a task:
- Name each tool after the question it answers or the action it performs, as a verb and an object. Start read tools with
getorlist. - Leave service names out, unless the same tool also fixes that service. A name does not scope a tool.
- In the description, say when to use the tool, when not to, and whether it only reads data or changes something.
- Only describe what the underlying tool actually does.
- Choose the final name now. You cannot change a virtual tool's identifier after you create it.
| Underlying tool | Exposed name |
|---|---|
list_entities | list_catalog_records |
list_blueprints | describe_catalog_record_types |
list_self_service_triggers | list_available_actions |
For each tool you rename, create a virtual tool and add it to the server:
-
Go to the Virtual Tools tab of the MCPs page.
-
Click + Virtual Tool.
-
Turn on JSON Mode.
-
Paste the virtual tool JSON, then click Register. For example, this virtual tool renames
list_blueprints:{"identifier": "describe_catalog_record_types","title": "Describe catalog record types","properties": {"name": "describe_catalog_record_types","tool": "list_blueprints","description": "List the types of records in the catalog and the fields each type has. Read only. Use it before listing records, to learn which fields you can filter on."}} -
Go back to the Virtual MCPs tab and open the
checkout-incident-triageserver for editing. -
Select the new virtual tool in Virtual Tools.
-
Remove the underlying tool from Passthrough Tools, so the agent does not see both versions, then click Update.
The next sections reuse this procedure for every virtual tool you create.
Scope catalog queries to the service
The agent can still query every service in the catalog. In this section, we fix a filter on list_entities so queries stay on checkout and the services connected to it, also known as its blast radius. With merge: append, the agent can still add its own filter rules, but the service filter is always applied.
Create the following virtual tool and add it to the server, as described in rename and describe tools for the job. It replaces the generic list_catalog_records, so remove list_catalog_records from the server:
{
"identifier": "list_checkout_records",
"title": "List checkout records",
"properties": {
"name": "list_checkout_records",
"tool": "list_entities",
"description": "List the checkout service and the catalog records related to it, such as its dependencies and the services that depend on it. Read only.",
"arguments": {
"query.combinator": { "value": "and" },
"query.rules": {
"value": [
{
"combinator": "or",
"rules": [
{ "property": "$identifier", "operator": "=", "value": "checkout" },
{ "operator": "relatedTo", "blueprint": "service", "value": "checkout", "direction": "upstream" },
{ "operator": "relatedTo", "blueprint": "service", "value": "checkout", "direction": "downstream" }
]
}
],
"merge": "append"
}
}
}
}
A few notes on this tool:
- The service name belongs in the tool name here, because the tool fixes that service.
- Fixing
query.combinatortoandmatters. Withor, the agent's own rules could widen the results past the service filter. - Use the service's identifier, not its title. Two records can share a title.
- A
relatedTorule only matches records whose blueprint has a relation path toservice. For more ondirection, see relation operators.
Limit changes to non-production
Write tools need stricter rules than read tools. In this section, we add back the ability to change things, but only through tools whose target is fixed. Fix every argument that decides what the tool changes, so production has no path.
Self-service actions
On trigger_run, fix the action with identifier and the trigger node with nodeIdentifier. The inputs argument is a free-form object, so you cannot fix one key inside it. Instead, create one virtual tool per allowed action and environment, and set the whole inputs object with value.
Create the following virtual tool and add it to the server:
{
"identifier": "restart_checkout_staging",
"title": "Restart checkout in staging",
"properties": {
"name": "restart_checkout_staging",
"tool": "trigger_run",
"description": "Restart the checkout service in staging. Changes the running service. Never affects production.",
"arguments": {
"identifier": { "value": "restart_service" },
"nodeIdentifier": { "value": "trigger" },
"inputs": { "value": { "workload": "checkout-staging" } }
}
}
}
Create a second tool for checkout-test the same way. Do not create one for checkout-production. The agent can still list every action with list_available_actions, but it can only run the ones you bound.
Catalog updates
An enum on the entity identifier is not enough on upsert_entity, since the identifier is optional and the agent can omit it. Create one virtual tool per record the agent may update, and fix both blueprintIdentifier and entity.identifier with value. The agent can still edit that record's properties and relations, and cannot reach any other record:
{
"identifier": "update_checkout_staging_workload",
"title": "Update checkout staging workload",
"properties": {
"name": "update_checkout_staging_workload",
"tool": "upsert_entity",
"description": "Update the checkout staging workload record in the catalog. Changes catalog data. Cannot update any other record.",
"arguments": {
"blueprintIdentifier": { "value": "workload" },
"entity.identifier": { "value": "checkout-staging" }
}
}
}
Update the server
When the scoped tools are in place, edit the checkout-incident-triage server:
- Check that Passthrough Tools does not include
trigger_run,upsert_entity, or a pattern that matches them, such as.*. - Update Instructions to match what the agent can now do. For example: "You can restart checkout in test and staging only. Any change to production is a recommendation for a person to run."
- Click Update.
Test the server
Reconnect your agent so it reloads the tool list, then send it the following prompts:
| Prompt | Expected result |
|---|---|
| "What tools do you have?" | Only the tools you exposed, under their new names. No list_entities, trigger_run, or upsert_entity. |
| "Which tool would you use to find the services checkout depends on, and why?" | The agent names list_checkout_records and quotes your description. |
| "List all services in the catalog." | Only checkout and the services related to it. |
| "Restart checkout in staging." | The agent calls restart_checkout_staging. |
| "Restart checkout in production." | The agent declines, because it has no tool that can change production. |
If a result does not match:
- The agent can still call a generic tool. Check that no pattern in Passthrough Tools matches it.
- A virtual tool does not appear. The calling user must have access to the underlying tool.
- The agent still sees old tools. Changes apply when the client reloads the tool list. Ask the agent to reload the server's tools, or reconnect the client.
Next steps
- Build agents: attach the virtual MCP server to a Port AI agent.
- MCP connectors: connect external MCP servers and expose their tools through your virtual MCP server.
- Manage MCP server registry: let teams request, review, and discover approved MCP servers.