Skip to content

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.

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.
  1. Selu calls your capability over gRPC (Invoke).
  2. For discovery, Selu calls the capability’s list_tools discovery tool.
  3. The wrapper starts an MCP subprocess (bring-mcp) and forwards discovery to MCP tools/list.
  4. Selu stores discovered tools and reconciles policies.
  5. Normal tool invocations are forwarded via MCP tools/call.

This keeps Selu compatibility at the boundary (gRPC), while reusing MCP internals unchanged.

  • Directoryselu-agent-bring-shopping/
    • agent.yaml
    • agent.md
    • Directorycapabilities/
      • Directorybring-shopping/
        • manifest.yaml
        • prompt.md
        • Directorycontainer/
          • Dockerfile
          • requirements.txt
          • capability.proto
          • server.py
  1. Declare the agent

    agent.yaml
    id: bring-shopping
    name: Bring Shopping Assistant
    routing: inline
    session:
    trigger: mention
    idle_timeout_minutes: 30
    memory:
    policy: none
    top_k: 0
  2. Use dynamic capability tools in manifest.yaml

    Instead of hardcoding the tool list, opt into dynamic discovery:

    capabilities/bring-shopping/manifest.yaml
    id: bring-shopping
    class: tool
    image: selu-cap-bring-shopping:latest
    tool_source: dynamic
    discovery_tool_name: list_tools
    tools: []
    credentials:
    - name: MAIL
    scope: user
    required: true
    description: "Bring account email"
    - name: PW
    scope: user
    required: true
    description: "Bring account password"
  3. Build a bridge that supports both invocation and discovery

    Your wrapper must support:

    • normal tool calls (tools/call)
    • discovery calls (tools/list) exposed as Selu list_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/list
    finally:
    bridge.close()
    return InvokeResponse(result_json=json.dumps(normalize(raw)).encode("utf-8"))

    The normalized discovery result must be a JSON array of objects:

    • name
    • description
    • input_schema
    • optional recommended_policy
  4. 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=latest
    RUN 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/*
  5. Guide the LLM with prompt.md

    Add explicit tool workflow instructions, for example: resolve default list first, then add/read/remove items.

  • Credential pass-through: Selu credentials arrive in config_json; map them to MCP process env vars.
  • Tool policy defaults: Include recommended_policy in discovery output (ask for mutating tools, allow for 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.