SAFi comes already packaged to handle MCP tool installation, including its own
registry search, which looks for MCP servers published at the official MCP
registry, https://registry.modelcontextprotocol.io
To search for an MCP server is easy. For example, if you want to see whether
Smartsheet has published an MCP server in the registry, you search for it with
this command.
On Docker:
docker compose exec app python scripts/safi_mcp.py search smartsheet
On bare metal, first move into your SAFi directory and activate the virtual
environment, then run the same script:
cd /var/www/safi
source venv/bin/activate
python3 scripts/safi_mcp.py search smartsheet
Your prompt changes to show (venv)
once the environment is active. That is how
you know it worked. If you skip this step, Python will not find SAFi’s
dependencies and the script will fail before it does anything.
Every bare-metal command in this post assumes you have done those two lines
first. When you are finished, deactivate
puts your shell back.
Here is what came back:
io.github.christianclaudio/smartsheet-rm (package, v1.0.2) [pypi:mcp-server-smartsheet-rm@1.0.2]
MCP server for Smartsheet Resource Management (10,000ft API) time tracking
and scheduling.
Add one with: scripts/safi_mcp.py add <name>
You may also see a few faiss
lines above that about AVX2 support. Ignore them.
That is the search index and it has nothing to do with MCP.
Four things in that one line tell you what you are dealing with.
** io.github.christianclaudio/smartsheet-rm** is the name you install by. Note
io.github
prefix: this is a server published by an individual developer,** (package, ...)** means it runs as a process on your own machine. The other
** v1.0.2** is the version, and it is pinned. Good. A server that updates can
** [pypi:...]** is the ecosystem.
pypi
means Python and SAFi will launch ituvx
. If it said npm
, SAFi would launch it with npx
. This detailBecause the registry knows the details, you install by name and SAFi works out
the command:
docker compose exec app python scripts/safi_mcp.py add io.github.christianclaudio/smartsheet-rm
python3 scripts/safi_mcp.py add io.github.christianclaudio/smartsheet-rm
On a fresh install this will refuse, and that is the system working. SAFi
checks that the launcher exists before it writes anything, so you get a message
naming the missing binary rather than a server that silently never starts:
'uvx' is not on PATH here, so this server could never start.
install uv (pip install uv), which provides uvx.
SAFi ships with Node and npx
, so npm
servers work out of the box. It does
not ship uv
, so a Python server needs it installed once:
pip install uv
RUN pip install uv
That last point matters on Docker. Installing uv
inside a running container
works until the next rebuild, and then the server stops connecting for a reason
that looks like nothing to do with you. Put it in the image.
Then run the add
command again and it will save.
If your search returns a remote server rather than a package, there is nothing
to install locally. You point SAFi at the URL:
docker compose exec app python scripts/safi_mcp.py add --url https://mcp.example.com/mcp \
--key example --label "Example"
python3 scripts/safi_mcp.py add --url https://mcp.example.com/mcp \
--key example --label "Example"
Some remote servers support per-user sign-in, so each member connects their own
account and every call runs as them. Add --auth oauth
for those. To find out
whether a server offers it, ask the server:
curl -s https://mcp.example.com/.well-known/oauth-authorization-server
If that returns a document, use --auth oauth
. If it returns nothing, the server
takes one static credential shared by everybody.
Prefer per-user sign-in whenever the data belongs to a person, which is exactly
the case with project tools. Each member’s calls then inherit their own
permissions in the source system, appear under their own name in that system’s
audit log, and stop working when they are offboarded. One shared credential is
the right model for a shared resource, such as an internal pricing service, and
the wrong one for somebody’s projects.
Most servers need an API token. The server’s own documentation tells you which
environment variable it reads. This one wants SMARTSHEET_RM_API_TOKEN
.
Pass it as a reference, never as the value:
python3 scripts/safi_mcp.py add io.github.christianclaudio/smartsheet-rm \
--env SMARTSHEET_RM_API_TOKEN='${SMARTSHEET_RM_API_TOKEN}' \
--env SMARTSHEET_RM_READONLY=true
Write ${SMARTSHEET_RM_API_TOKEN}
literally, exactly as shown, braces and
quotes included. SAFi resolves it from the environment when it connects. The
real token goes in your .env
file, so the server list stays safe to copy, back
up and share with a colleague.
If the server offers a read-only switch, as this one does, turn it on for the
first install. You can relax it later once you know what the tools return.
This is the thing to understand before you give a tool to anybody.
A package server is a process on your machine holding one API token. Every
member’s agents act as whoever owns that token. Everyone sees whatever that
account can see, the source system’s audit log records that account rather than
the person who asked, and access does not end when someone leaves the company.
That is the correct model for a shared resource, such as an internal pricing
service or a company API. It is the wrong model for “show me my projects”,
because there is no “my” in it.
It does not matter that the service has user logins of its own. Smartsheet has
accounts and permissions, but a local server reaches it with one token, so all
of that collapses into a single identity on the way through.
Per-user sign-in exists in SAFi, and it is the answer to this, but it needs a
remote server that implements the MCP authorization specification. A local
process cannot take part. That is why some cards in the Tools Catalog have aSign in button and others do not.
So choose deliberately. Use a service account with the narrowest permissions
that still make the tools useful, and enable only read tools to begin with. If
members genuinely need their own view of their own data, a shared token is not
the way to get there.
docker compose exec app python scripts/safi_mcp.py check
docker compose exec app python scripts/safi_mcp.py list
python3 scripts/safi_mcp.py check
python3 scripts/safi_mcp.py list
check
connects to every configured server and reports what it found. list
shows what is configured and where it came from.
You do not need to restart anything. Every write bumps a counter that the
running workers watch, so they pick up the new server on their next request.
“Connected” does not mean authenticated. When the Tools Catalog shows a
server as connected, it means the process started and answered SAFi’s question
about what tools it has. That is a local conversation between SAFi and that
process. Nothing has contacted the vendor yet.
So a server with a missing or wrong API token still shows as connected, with its
full tool list. You find out on the first real tool call, which comes back as an
authentication error from the vendor rather than as a connection problem in the
catalog. The green state is real, it just certifies less than it looks like: the
server is installed and running, not that it can reach anything.
This is the step people skip.
Installing a server puts it in Settings, Tools Catalog, where the server and its
tools are visible and completely inactive. Nothing can call them yet.
Discovery never grants anything.
Three steps make a tool usable:
Start with the read-only tools. Get those working, then decide about anything
that creates, updates, moves or deletes, and give that one a reviewer.
Two things to know before you enable a server you did not write. Its tool
descriptions are written by the publisher and go into the model’s context. And a
tool result becomes part of the governance record, so whatever it returns
inherits that record’s retention.
Installing an MCP server is done from the shell, by whoever controls the
deployment. That is the only way, for remote servers as much as for packages.
There is no admin screen for it and no API route, on purpose.
The browser installs nothing. Settings, Tools Catalog is where you see what is
installed, where members sign in to servers that support it, and where you
confirm what a server offers. It is not a place to add one.
This is deliberate rather than unfinished. Installing a server is a decision
about what code runs next to your data and what leaves your network, and whoever
makes it should already hold the rights that implies. On a deployment serving
several organizations, an admin who could install a server would be making that
decision for everybody on the host, not just for their own organization.
What an organization admin controls is the part that matters day to day: which
of the installed tools their policies enable, and which agents get them.
There are seven. Everything in this post is one of these.
scripts/safi_mcp.py search <term> # find servers in the official registry
scripts/safi_mcp.py add <name> # install one
scripts/safi_mcp.py list # show what is configured, and where from
scripts/safi_mcp.py check # connect to everything and report
scripts/safi_mcp.py disable <key> # stop connecting, keep the definition
scripts/safi_mcp.py enable <key> # start connecting again
scripts/safi_mcp.py remove <key> # delete the definition entirely
disable
and remove
are the pair worth understanding. disable
keeps
everything you configured and just stops SAFi connecting to it, which is what
you want when a server is misbehaving and you are still working out why.remove
deletes the definition, so reinstalling means retyping the arguments,
the credentials and the org list.
Neither of them changes what a policy allows. If you remove a server, the tools
it provided simply stop existing, and the Will refuses any call naming them.
check
takes an optional --key
to test one server instead of all of them,
which is faster when you are debugging a single install.
The flags on add
you are most likely to need:
--url a remote server's endpoint, instead of a registry name
--auth oauth the server supports per-user sign-in
--command the local program to run, for a package server
--args comma-separated arguments for --command
--env KEY=VALUE for the server process, repeatable
--key the short name you will use in every other command
--label the display name people see in the tool picker
--orgs which organizations may use it, on a shared deployment
--force install even if the check fails
One trap with --args
. If your first argument starts with a dash, use the
equals form or argparse reads it as another option:
--args="-y,@scope/package@1.2.3"
Use --force
sparingly. The check exists so a server that cannot start fails
now, while you are looking at it, rather than becoming a mystery next week.
Every SAFi install ships with a small demo MCP server so you can watch this work
end to end:
docker compose exec app python scripts/safi_mcp.py add \
--command python --args "/app/mcp/demo_server.py" --key demo --label "Demo Server"
python3 scripts/safi_mcp.py add \
--command python3 --args "/var/www/safi/mcp/demo_server.py" --key demo --label "Demo Server"
It has two tools. Install it, enable one of them in a policy, and watch an agent
get exactly that one. Then ask the agent to use the other one, and read the
governance record for that turn.
Run it and tell us where it fails.