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 smartsheetOn 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 smartsheetYour 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.
Reading the result
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 loading 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
the io.github prefix: this is a server published by an individual developer,
not by Smartsheet. It is a community package that talks to Smartsheet’s API, not
an official product. That is not a reason to avoid it, but it is a reason to
read what it does before you grant anything.
(package, ...) means it runs as a process on your own machine. The other
kind you will see is a remote server, which is just a URL that the publisher
runs and which needs nothing installed locally.
v1.0.2 is the version, and it is pinned. Good. A server that updates can
change what a tool does behind a name that never changed.
[pypi:...] is the ecosystem. pypi means Python and SAFi will launch it
with uvx. If it said npm, SAFi would launch it with npx. This detail
decides whether you need to install anything first, which is the next section.
Installing it
Because the registry knows the details, you install by name and SAFi works out
the command:
# Docker
docker compose exec app python scripts/safi_mcp.py add io.github.christianclaudio/smartsheet-rm
# Bare metal
python3 scripts/safi_mcp.py add io.github.christianclaudio/smartsheet-rmOn 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:
# Bare metal, with the venv active
pip install uv
# Docker, add it to your Dockerfile and rebuild, otherwise it disappears
# on the next `docker compose up --build`
RUN pip install uvThat 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.
Installing a remote server instead
If your search returns a remote server rather than a package, there is nothing
to install locally. You point SAFi at the URL:
# Docker
docker compose exec app python scripts/safi_mcp.py add --url https://mcp.example.com/mcp \
--key example --label "Example"
# Bare metal
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-serverIf 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.
Passing a credential
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:
# Bare metal
python3 scripts/safi_mcp.py add io.github.christianclaudio/smartsheet-rm \
--env SMARTSHEET_RM_API_TOKEN='${SMARTSHEET_RM_API_TOKEN}' \
--env SMARTSHEET_RM_READONLY=trueWrite ${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.
A local server is always one shared identity
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 a
Sign 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.
Check your work
# Docker
docker compose exec app python scripts/safi_mcp.py check
docker compose exec app python scripts/safi_mcp.py list
# Bare metal
python3 scripts/safi_mcp.py check
python3 scripts/safi_mcp.py listcheck 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.
Nothing is usable until you grant it
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:
- Policy. In a policy’s Tools and Guardrails step, enable the specific
tools that agents under that policy may use. Tool by tool, so you can enable
the one that lists projects and leave the one that deletes them switched off. - Agent. Assign the tools the policy allows. That is what gets advertised
to the model. - The Will. Every single call is checked against that list by exact name
before it runs.
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.
Who is allowed to install what
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.
All the commands
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 entirelydisable 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 failsOne 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.
Try it
Every SAFi install ships with a small demo MCP server so you can watch this work
end to end:
# Docker
docker compose exec app python scripts/safi_mcp.py add \
--command python --args "/app/mcp/demo_server.py" --key demo --label "Demo Server"
# Bare metal
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.

