Docs / Guide / Bring an existing MCP server

Deploy the connector

The connector is the small server ekl agent init sets up beside your MCP server (From an existing MCP server). It has to run somewhere with an https address before your agent can announce itself. ekl agent deploy puts it there with one command, from the agent's folder.

What it does. It reads agent.json and .env from the folder, starts the connector at the destination you choose with your own tools and sign-in (uv, systemctl, docker or the AWS CLI), writes the connector's address into agent.json as adapter.endpoint, and ends with the review step. It runs on your machine only: it calls no EuKollab service, and nothing about the deployment reaches the network except the connector's own announcement when it starts.

It deploys the connector only. An adapter you attached inside your own server (--adapter attach) is deployed with that server, as you deploy it today: ekl agent deploy refuses an agent.json without upstream.

The commands

Run them in the agent's folder (bay-homes-connector in these examples), or pass --dir <folder>:

ekl agent deploy                          # the destinations, and what each needs, checked on this machine
ekl agent deploy <destination> --check    # what it needs only; each missing thing says how to fix it
ekl agent deploy <destination> --print    # what it would do, secrets hidden; nothing is done
ekl agent deploy <destination>            # deploy; run it again to update the same deployment
ekl agent deploy <destination> --remove   # take it down; the agent stays registered

Options for every destination:

Option What it does
--dir <folder> The agent's folder, with agent.json and .env. Default: the current folder
--name <name> The deployment's name. Everything it creates is called ekl-<name>. Default: the handle without @, so ekl-bay-homes
--size small\|medium How much the connector gets. Default small, enough for most agents

Each destination has options of its own: ekl agent deploy <destination> --help lists them.

Choose a destination

Destination For What you need Your MCP server must be reachable from What ekl creates Where the secrets are kept
local Trying it on your machine Python 3.13, or uv This machine Nothing lasting: a running connector, and with --background its pid and log in <folder>/.ekl Read from .env by the running process
systemd A Linux server you run, behind your own reverse proxy Linux with systemd, sudo or root, Python 3.13 or uv, an https address your proxy serves That server A system user ekl-<name>, the connector in /opt/ekl/<name>, a service that restarts it /etc/ekl/<name>.env, readable only by the service's user
docker Any machine with Docker docker with a running daemon; the image (--build for now) Inside the container The container ekl-<name>, restarting unless stopped In the container's configuration: anyone who can run docker inspect on that machine can read them
aws-lightsail AWS with the least setup: Lightsail gives the connector its own https address AWS CLI v2, signed in, a region, the Lightsail permissions; the image (--build for now, which also needs Docker and the lightsailctl plugin) AWS The container service ekl-<name> As environment values of the deployment: anyone in the AWS account allowed to read its deployments can read them
aws-ecs AWS with the secrets in Secrets Manager, behind a load balancer AWS CLI v2, signed in, a region, the ECS, Secrets Manager, IAM, Elastic Load Balancing, CloudWatch Logs and EC2 permissions; a VPC with two subnets or more; a host name and a load balancer or an ACM certificate; the image (--build for now) AWS A Fargate service, its task definition, an execution role, a log group, a target group, a listener rule, and with --create-load-balancer the load balancer The Secrets Manager secret ekl-<name>, readable only by the service's execution role

What it costs. local, systemd and docker use machines you already have. Lightsail and ECS charge by the size you run, at AWS's prices for your region: ekl doesn't quote them. For Lightsail, aws lightsail get-container-service-powers lists each size's CPU, memory and price; --size small runs nano, medium runs micro. On ECS, small is 0.25 vCPU and 0.5 GB, medium 0.5 vCPU and 1 GB, plus the load balancer.

Your MCP server's address. The connector calls your server at EKL_UPSTREAM_MCP_URL in .env. An address like http://localhost:8000/mcp works only where the connector runs on the same machine. For docker, use http://host.docker.internal:8000/mcp (Docker Desktop) or the server's address on your network. For AWS, use an address AWS can reach. --check says so when the address won't work there.

The network reaches the connector at adapter.endpoint, which must be public and use https. Don't put your own authentication in front of it: the connector proves to the network that it holds the agent key, and the network authenticates every call.

local

Runs the connector on this machine, on 127.0.0.1, to try it.

cd bay-homes-connector
ekl agent deploy local --check
ekl agent deploy local
Option What it does
--port <port> The port it listens on. Default 8001 (MCP servers often use 8000): pick another if it is taken
--background Keeps it running after the command ends, with its pid and log in .ekl/
--endpoint https://… Its public address, for example a tunnel's, when the network should reach it here

What you need: uv, or Python 3.13. With uv it runs the connector straight from the network's package index; without it, it installs the connector in a virtual environment in .ekl/ first.

You should see the address it set and the review step, then the connector starting in the foreground (Ctrl-C stops it):

deploying the connector of @bay-homes to local as ekl-bay-homes
deployed the connector of @bay-homes to local: http://localhost:8001/ekl

Next:
  1. The connector announces agent.json when it starts. See what it announced:  ekl agent review @bay-homes
  2. Confirm it:  ekl agent confirm @bay-homes, or in the console under Agents (Ready for review)
Running this again updates the deployment; --remove takes it down.

starting the connector (Ctrl-C stops it)…

The network can reach a connector on localhost only through a tunnel you run: pass the tunnel's https address with --endpoint https://…/ekl. Otherwise use local to see the connector read your server and announce the agent, then deploy it for real.

ekl agent deploy local --remove stops a --background connector and deletes .ekl/.

systemd

A service on a Linux server you run, behind your own reverse proxy (nginx, Caddy or another). Run ekl on that server, in the agent's folder.

ekl agent deploy systemd --endpoint https://agents.bayhomes.example/ekl --check
ekl agent deploy systemd --endpoint https://agents.bayhomes.example/ekl
Option What it does
--endpoint https://… Required: the public address your reverse proxy serves, ending in /ekl
--port <port> The local port behind the proxy. Default 8001

What you need: Linux with systemd, sudo (or run as root), Python 3.13 or uv, and a domain your reverse proxy serves over https.

What it creates: the system user ekl-<name>; the connector in a virtual environment under /opt/ekl/<name>; its settings in /etc/ekl/<name>.env, readable only by that user; and the service ekl-<name>, which restarts the connector if it stops. The connector listens on 127.0.0.1 only.

You should see the deployed address, then the configuration for your proxy:

Serve it at https://agents.bayhomes.example/ekl from your reverse proxy (the path is passed on unchanged):
  nginx, in the server block for agents.bayhomes.example:
    location /ekl { proxy_pass http://127.0.0.1:8001; proxy_set_header Host $host; proxy_buffering off; }
  Caddy (Caddyfile):
    agents.bayhomes.example { handle /ekl* { reverse_proxy 127.0.0.1:8001 } }

Add it to your proxy and reload it. If the service doesn't start, ekl says so and how to see why: journalctl -u ekl-bay-homes -n 50.

For a durable agent on SQLite, set EKL_ADAPTER_STORAGE to a file under /var/lib/ekl-<name>, the only folder the service may write to.

docker

A container on this machine's Docker.

ekl agent deploy docker --build --check
ekl agent deploy docker --build
Option What it does
--build Builds the connector image here, then runs it
--build-only Builds the image and stops
--image <image> Runs an image you have instead
--port <port> The port on this machine. Default 8001
--endpoint https://… Its public address, when something in front of it serves one

What you need: the docker command and a running Docker daemon, and the image (The image).

What it creates: the container ekl-<name>, restarting unless you stop it, published on 127.0.0.1 only. Its settings are passed from a file only you can read, deleted once the container starts. Docker keeps them in the container's configuration, so anyone who can run docker inspect on this machine can read them.

Put a reverse proxy in front of it for a public https address, and pass that address with --endpoint.

aws-lightsail

An Amazon Lightsail container service, which gives the connector its own https address. The least to set up on AWS.

ekl agent deploy aws-lightsail --build --check
ekl agent deploy aws-lightsail --build
Option What it does
--region <region> The AWS region. Default: AWS_REGION, else your configured one
--profile <profile> The AWS CLI profile. Default: your default one
--build Builds the image with Docker and pushes it to the service's own registry
--image <image> Runs an image you have instead

What you need: - The AWS CLI v2, signed in (aws configure sso, aws login or aws configure), with a region. - Permission to create, update, deploy, tag and delete Lightsail container services, and with --build to push images to them. --check asks AWS whether you have them and, when you don't, prints the least IAM policy to attach. - With --build: Docker and the lightsailctl plugin. - Your MCP server at an address AWS can reach.

What it creates: the container service ekl-<name>, with the health check on /healthz. It waits for the service's https address, which can take several minutes, writes it into agent.json and gives it to the connector.

The secrets. Lightsail containers can't read AWS Secrets Manager, so the agent key and your server's address are environment values of the deployment. Anyone in your AWS account allowed to read the service's deployments can read them. To keep them in Secrets Manager, use aws-ecs.

aws-ecs

An AWS Fargate service behind an Application Load Balancer, with the secrets in Secrets Manager.

ekl agent deploy aws-ecs --build --vpc vpc-0abc --subnets subnet-0aa,subnet-0bb \
  --host agents.bayhomes.example --load-balancer my-alb --check
ekl agent deploy aws-ecs --build --vpc vpc-0abc --subnets subnet-0aa,subnet-0bb \
  --host agents.bayhomes.example --load-balancer my-alb
Option What it does
--vpc <vpc> The VPC to run in
--subnets <a>,<b> Subnets in it, in two availability zones or more. --check suggests your default VPC's
--host <name> The public host name the connector answers on, covered by the load balancer's certificate
--path <path> The path on that host. Default /ekl
--load-balancer <name or ARN> An Application Load Balancer you have
--listener <ARN> Its HTTPS listener, when it isn't on port 443
--create-load-balancer Creates a load balancer for this agent instead; needs --certificate
--certificate <ARN> The ACM certificate for a created load balancer, covering --host, in the same region
--cluster <name> An ECS cluster you have. Default: creates ekl-<name>
--region, --profile, --build, --image As for aws-lightsail; --build pushes to the private ECR repository ekl-provider-connector

What you need: - The AWS CLI v2, signed in, with a region. - Permissions in ECS, Secrets Manager, IAM (to create the connector's own role), Elastic Load Balancing, CloudWatch Logs and EC2 (security groups), and ECR with --build. --check asks AWS whether you have them and, when you don't, prints the least IAM policy to attach. - A VPC, a host name in a domain you control, and a load balancer or an ACM certificate. - Your MCP server at an address AWS can reach.

What it creates: the Secrets Manager secret ekl-<name>, holding the agent key, your server's address and the other secret values; an execution role that may read only that secret; the log group /ekl/<name>; a Fargate task definition; a target group that checks /healthz; a listener rule for --host and --path; and the service. It waits until the service is steady. The address is https://<host><path>.

Then point --host at the load balancer in your DNS. The connector's log: aws logs tail /ekl/<name> --follow.

Check before you deploy

--check lists everything the destination needs, marks each ✓ or ✗, and says how to fix each missing one. It changes nothing. For example, on a laptop:

docker: a container on this machine's Docker
  ✓ agent folder .: agent.json for @bay-homes (connector) and .env
  ✓ docker
  ✓ the Docker daemon is running
  ✗ image public.ecr.aws/eukollab/ekl-provider-connector:0.2.0 (the planned public image)
      the public connector image isn't published yet: pass --build (docker builds it here and runs it), or --image <an image you built from provider-adapter's Dockerfile>, or set EKL_CONNECTOR_IMAGE
  ✗ EKL_UPSTREAM_MCP_URL reachable from inside the container
      in a container localhost is the container itself: set EKL_UPSTREAM_MCP_URL in .env to http://host.docker.internal:<port>/… (Docker Desktop) or the server's address on your network

not ready: fix what is marked ✗

Deploying stops before it changes anything when something is missing, with the same list.

After deploying

  1. The connector announces agent.json when it starts. See what it announced: ekl agent review @bay-homes.
  2. Confirm it: ekl agent confirm @bay-homes, or in the console under Agents (Ready for review) (Review, confirm and the checks).
  3. Follow the checks: ekl agent status @bay-homes --watch.

Update and remove

Update. Run the same command again. It finds what it created before and updates it: a new agent.json, a new key after ekl agent rotate-key, or a new connector release. A changed agent.json is announced as a new version and waits for your review; the version you confirmed keeps serving until then (An agent's lifecycle).

Remove. ekl agent deploy <destination> --remove takes away exactly what it created. In a terminal it asks first. On AWS it removes only resources carrying both the agent's tags, ekl:agent=<handle> and ekl:name=ekl-<name>, and never changes a same-named resource without them. Add --print to see what it would remove.

The agent stays registered. To take it off the network for good, retire it in the console (Agents → the agent → Retire). To run it elsewhere, deploy it there.

The image

docker, aws-lightsail and aws-ecs run the connector image. The public image is planned and not yet published. Until it is, use one of:

  • --build: Docker builds the image on your machine from the network's package index (for AWS, for linux/amd64, whatever your machine is) and, for AWS, pushes it to the Lightsail service's own registry or to the private ECR repository ekl-provider-connector;
  • --image <image>: an image you built and pushed yourself;
  • EKL_CONNECTOR_IMAGE=<image> in your environment, the same as --image on every run.

--check says which applies. local and systemd need no image.

Troubleshooting

What you see What to do
isn't ready on this machine; nothing was changed Fix each line marked ✗; --check shows the same list
the public connector image isn't published yet Add --build, or --image <image> (The image)
EKL_UPSTREAM_MCP_URL reachable from … Your server's address is localhost, which the container or AWS can't reach. Set EKL_UPSTREAM_MCP_URL in .env to an address that works there
signed in to AWS marked ✗ Sign in with aws configure sso, aws login or aws configure; pass --profile if it isn't your default
permissions: … not allowed Attach the policy --check prints to your user or role. AWS checks it for every resource, so a policy limited to ekl-* resources can show as missing and still work
permissions: not checked You can't ask AWS's policy simulator. Attach the printed policy, or deploy and read what AWS refuses
Port already in use, with local Something else listens on 8001: pass another, for example --port 8002
the connector stopped before it answered With --background, the end of its log is printed; it's in .ekl/connector.log
ekl-<name> didn't start (systemd) journalctl -u ekl-<name> -n 50 shows why
Nothing appears under Agents The connector couldn't announce the agent: read its log, and see Troubleshooting
--remove says it found nothing Pass the same --name and --region you deployed with

What's next