Pattern: Wrap Existing MCP Tools
If you already have a working MCP server, the fastest path to a Selu capability is often a wrapper, not a rewrite.
This page shows the pattern using bring-mcp (Bring! shopping lists) and the example agent selu-agent-bring-shopping.
When to use this pattern
Section titled “When to use this pattern”Use a wrapper when:
- The upstream MCP server is already stable and tested.
- You want to ship quickly with minimal custom integration code.
- You want Selu to discover tools dynamically instead of manually copying tool schemas into
manifest.yaml.
Architecture
Section titled “Architecture”- Selu calls your capability over gRPC (
Invoke). - For discovery, Selu calls the capability’s
list_toolsdiscovery tool. - The wrapper starts an MCP subprocess (
bring-mcp) and forwards discovery to MCPtools/list. - Selu stores discovered tools and reconciles policies.
- Normal tool invocations are forwarded via MCP
tools/call.
This keeps Selu compatibility at the boundary (gRPC), while reusing MCP internals unchanged.
Example project structure
Section titled “Example project structure”Directoryselu-agent-bring-shopping/
- agent.yaml
- agent.md
Directorycapabilities/
Directorybring-shopping/
- manifest.yaml
- prompt.md
Directorycontainer/
- Dockerfile
- requirements.txt
- capability.proto
- server.py
Step-by-step
Section titled “Step-by-step”-
Declare the agent
agent.yaml id: bring-shoppingname: Bring Shopping Assistantrouting: inlinesession:trigger: mentionidle_timeout_minutes: 30memory:policy: nonetop_k: 0 -
Use dynamic capability tools in
manifest.yamlInstead of hardcoding the tool list, opt into dynamic discovery:
capabilities/bring-shopping/manifest.yaml id: bring-shoppingclass: toolimage: selu-cap-bring-shopping:latesttool_source: dynamicdiscovery_tool_name: list_toolstools: []credentials:- name: MAILscope: userrequired: truedescription: "Bring account email"- name: PWscope: userrequired: truedescription: "Bring account password" -
Build a bridge that supports both invocation and discovery
Your wrapper must support:
- normal tool calls (
tools/call) - discovery calls (
tools/list) exposed as Selulist_tools
capabilities/bring-shopping/container/server.py if request.tool_name == "list_tools":bridge = McpBridge(mail_or_dummy, pw_or_dummy)bridge.start()try:raw = bridge.list_tools() # MCP method: tools/listfinally:bridge.close()return InvokeResponse(result_json=json.dumps(normalize(raw)).encode("utf-8"))The normalized discovery result must be a JSON array of objects:
namedescriptioninput_schema- optional
recommended_policy
- normal tool calls (
-
Package runtime dependencies in Dockerfile
Install both sides:
- Python dependencies for gRPC server + generated stubs.
- Node + upstream MCP package (
bring-mcp).
capabilities/bring-shopping/container/Dockerfile ARG BRING_MCP_NPM_VERSION=latestRUN apt-get update \&& apt-get install -y --no-install-recommends nodejs npm \&& npm install -g "bring-mcp@${BRING_MCP_NPM_VERSION}" \&& rm -rf /var/lib/apt/lists/* -
Guide the LLM with
prompt.mdAdd explicit tool workflow instructions, for example: resolve default list first, then add/read/remove items.
Practical notes
Section titled “Practical notes”- Credential pass-through: Selu credentials arrive in
config_json; map them to MCP process env vars. - Tool policy defaults: Include
recommended_policyin discovery output (askfor mutating tools,allowfor read-only tools). - Policy reconciliation: Selu adds policies for new tools and removes policies for deleted tools.
- Network policy: Start with a strict allowlist for known upstream hosts.
- Error handling: Preserve actionable upstream errors and avoid leaking secrets in logs.
- Version pinning: Pin the MCP package version in Docker to avoid accidental breaking changes.