23 tools, one connector

The twenty-three tools

Add the connector once and these sit beside everything else your assistant can do. Each one is listed by the name it is called by and by what its code actually does.

This page is for whoever is wiring it up. It is the reference, not the pitch, and it names things by the strings a machine matches on. The landing page makes the same argument in plain words.

connector url23 tools
https://mcp.juudd.com/mcp

A tool appears only when the thing behind it exists. A control plane with no deploy target or no database provider offers fewer, and the tool list is the clearest signal a client gets about what is configured. All 23 are listed here.

you:put this notes app online for me

tool:juudd · deploy_site · 3 files and functions/api/notes.js, stored as v1

tool:juudd · create_database · one Postgres, for this site only

tool:juudd · apply_sql · notes table, and the policy that says who can read a row

answer:Live at https://probe.juudd.app. Every note belongs to whoever wrote it.

you:change the heading to Today

tool:juudd · deploy_site · 3 files, stored as v2

answer:v2 is live. v1 is still in the store, and the database was not touched.

you:add a table for the notes

refused:juudd · apply_sql · refused

answer:notes has no row-level security policy. A table cannot be deployed against until it carries one.

you:add the policy then

tool:juudd · apply_sql · 2 statements, one transaction

answer:notes created, policy on user_id. The deploy gate will pass it now.

you:that last deploy broke the list, put yesterday's back

tool:juudd · list_deploys · 4 stored versions, v4 is current

tool:juudd · publish_deploy · v3

answer:v3 is live again. v4 is still in the store; nothing was deleted.

you:and the notes people wrote today?

answer:Untouched. A publish changes the files, never the database.

shell:curl -H 'x-user: alice' https://probe.juudd.app/api/notes

answer:{"who":"alice","rows":[{"user_id":"alice","body":"alice keeps notes"}]}

shell:curl -H 'x-user: bob' https://probe.juudd.app/api/notes

answer:{"who":"bob","rows":[{"user_id":"bob","body":"bob keeps his own"}]}

shell:curl https://probe.juudd.app/api/notes

answer:{"who":null,"rows":[]}

Every tool name and refusal above is one juudd really makes. The last tab is the live demo.

The connection

Two tools that take no site, and therefore the only two with nothing to authorise. One says which account you are; the other says what that account has been told no about.

hello

Hello

Returns a greeting and the account this connection is acting as. It proves the connection works end to end, and it names the account that owns anything deployed through it. Every refusal anywhere else is about that account, and this is the one place you can read it.

list_refusals

List refusals

Lists what this account has been told no about, newest first, in the exact words it was told them in. A deploy a schema check stopped, a site somebody else owns, SQL against a database that has already been handed over. Only the refusals: what worked is what list_deploys already shows. It is how an assistant picking your project up in a later conversation learns why the last attempt failed, instead of asking you.

Deploys

Every version is kept and every version can be read back. Publishing an older one is how a rollback happens, and it is the same code path a recovery runs.

deploy_site

Deploy a site

Stores a recoverable bundle of a site, its files, subdomain and version, in the artifact store, then publishes it and returns the live URL. Each file arrives as text or as base64: text for HTML, CSS and JavaScript, base64 for images, fonts, icons and anything else that is not text. Server code goes in functions, where the path spells the URL, so functions/api/orders.js answers /api/orders and every other URL is served from the files. Routing can be given as static or spa, and when it is not given it is inferred; the reply says which was used. Publishing reads the bundle back out of the store rather than reusing what is in memory, so what gets served is what can be recovered.

request_upload

Get a link to upload a built site

Returns a one-time link and a short Node script that posts a built folder to it, for a project a framework built: React or Vue with Vite, Next.js, Nuxt, SvelteKit, Astro. Nothing is built here. The project is built where its code is and the output folder is sent, dist or out or .output/public, text as text and everything else as base64, under the same 4 MB bound. The post stores a version exactly as deploy_site would and publishes nothing; publish_deploy puts it live. Server code is not part of a build, so framework API routes and server rendering do not come across; plain-JavaScript functions are sent beside the folder. The link works once, for fifteen minutes.

start_build

Build a project from its source

Builds a project a framework needs building, React or Vue with Vite, Next.js with output export, Nuxt, from its source on this platform, and stores the result as a version of the site; publish_deploy puts it live. This is the door for an assistant that has no shell, ChatGPT or claude.ai. Nothing is installed from your package.json: the build runs in an isolated sandbox against a toolchain this platform pins, holding no credential of ours and reaching nothing but the package registry, and a dependency outside the toolchain is refused by name with the list in the reply. Server code is not built; files under functions/ pass through as plain-JavaScript functions. A quick build finishes inside the reply; a longer one hands back a job for build_status. The first build for an account, and the first after a quiet spell, takes a minute or two longer while the toolchain is installed. One build at a time per account.

build_status

Check a build

Reports a build start_build started: still running, done with the stored version, or failed with the last lines the build printed. A build takes ten seconds to a few minutes. When it is done, publish_deploy with the site puts the version live.

publish_deploy

Publish a stored deploy

Publishes a version that is already in the artifact store, the newest one by default or any earlier one by name. Rolling back is publishing an earlier version, which is the same code path a disaster recovery runs, so the rollback is exercised every time anyone deploys.

unpublish_site

Stop serving a site

Takes a site off the air. Visitors stop getting it immediately, and nothing is deleted: every stored version stays where it was, the database is untouched, the site id stays yours, and publish_deploy puts it back on the version that was live. It is the tool for a shop that has closed, a page that went out wrong, or a launch that has to wait — deploying a page that says closed is a different thing, because the old page is still there for anyone holding the URL. One thing does not come back on its own: the values of your secrets live on the script this removes, and this platform cannot read them or copy them, so you set them again once the site is serving. The reply lists them by name.

list_deploys

List deploys

With no argument, lists the sites this account owns. With a site id, lists every stored version of that site, newest last, marking which one is current. Sites belonging to another account are not listed and cannot be read.

request_secret

Ask for a secret to be set on a site

Declares a secret a site’s functions will read as env.NAME, an API key or a webhook signing secret, and returns a link into the panel where you paste the value yourself. The value never passes through the assistant, on purpose: a key in a tool call is a key in the transcript and in every log between, so no tool here takes one. The site has to have been deployed once first, because the value is written by republishing its current version with one more binding; after that every deploy carries it forward without anyone reading it. DATABASE_URL is not one of these.

list_secrets

List a site’s secrets

Lists the secret names a site’s functions can read, and whether each has been given a value yet. Never a value: the platform writes them onto the site and cannot read them back. It is how an assistant checks that a key you were asked to set has arrived before debugging the route that reads it.

read_site

Read a deployed site back

Reads back what was deployed: the file list of a stored version, or the contents of one file. It is how a site written in one conversation is changed in a later one, by an assistant that was not there when it was written, and it is how the code leaves here at all. Text comes back as text, including source this platform has no content type for; binary files are listed with their size and fetched from the site itself. It reads only what that version’s own manifest lists, so a path cannot be pointed at anything else.

write_site_brief

Write down what a site is for

Keeps a short brief with a site: what it is for, what its tables hold, what you decided and what you want next. read_site shows it above the files, so the next assistant to work on the site — in another conversation or another app — starts from what you meant, not only from the code. Each writing replaces the brief and the earlier ones are kept, so nothing is lost by rewriting it. At most 6,000 characters; never a password or a key.

site_errors

Which routes of a site failed

Says which of your sites have failed in front of visitors recently, or, for one site, which routes failed, when, and how many times — the runtime’s own verdict: 503 means the request was stopped at a resource limit, 500 means the code threw or made too many outbound requests, and those two cannot be told apart. It is not what a function printed; there is still no way to read that. The record is counted per route and per hour, kept seven days, and best-effort: a failure the platform did not hear about is not in it. No query string, header or body is ever kept.

The database

One Postgres database per site. A table without a row-level security policy cannot be deployed against, and the exit is one call with no deadline on it.

create_database

Create a database

Creates a Postgres database for a site. One per site: calling again returns the existing one rather than making a second. It returns the owner connection string, for anyone who has a terminal, and that string must not go into the site files or into a function, because deployed code is public. The site's own functions are given a separate, restricted connection string automatically at deploy time, as env.DATABASE_URL.

apply_sql

Run SQL on a site’s database

Runs SQL against a site's database as the owner. This is how tables, policies and seed data get made, and how they are read back. Every statement runs in one transaction: if any of them fails, none of them applied. Each table and its row-level security policy belong in the same call, because a table without one cannot be deployed against. The reply says what every statement returned and what the deploy gate would still refuse.

database_status

Database status

Says whether a site has a database, and whether this platform still holds it.

transfer_database

Hand the database over to its owner

Returns a one-time URL that moves the database into your own account, after which this platform loses all access to it: it can no longer read the database, back it up, or get it back. This is the exit door and it has no deadline. A new URL can be minted whenever it is asked for, however old the database is, so an expired link is not a window you missed.

set_database_url

Give a site a new database connection string

Writes a connection string onto a deployed site as env.DATABASE_URL, by republishing the site's current stored version: the same files, unchanged, with a different binding. It exists for one situation and refuses the rest. After transfer_database you own the database, and the only copy of the credential your site is using is a write-only binding on the site itself, which this platform cannot read, copy or work out. This is what puts it back. The value is not stored, not read back and not tested, so check it by loading the site.

reset_database_credential

Rebuild a site’s database credential

Repairs a site that can no longer reach the database this platform holds for it, because the restricted role its functions connect as, juudd_app, was dropped or re-passworded by hand from a terminal. The role is created again with NOBYPASSRLS, given a fresh password, granted only what the schema gate allows over the tables that exist right now, and the site's current stored version is republished to carry it. It is a rotation, so the old password stops working immediately, and nothing on this side can tell a wrong password from a right one. Be sure of the diagnosis first.

Your own domain

A person has to add one DNS record in the middle of this, at whoever runs their domain. No tool here does it, and no tool here can see them doing it, which is why one tool claims and a different one answers questions.

claim_domain

Point a customer’s own domain at a site

Claims a domain you already own for one of your sites and returns the exact CNAME record to add at your DNS provider. This platform does not register domains; you bring one you hold. The site keeps its original address as well. Both work, and neither redirects to the other. One domain belongs to one site: if another site has already claimed it, this says so and changes nothing, and www.example.com and example.com are two separate claims. Two domains cannot be onboarded: a bare apex like example.com works only if your provider flattens CNAMEs or offers ALIAS or ANAME records, and a domain already served through another CDN cannot be validated while that CDN is in front of it.

domain_status

Check a claimed domain

Says where a claimed domain has got to, and moves it forward once it is ready. Two separate things must be true before it serves: the domain has to be proven to point here, and a certificate has to be issued for it. This reports both, because a domain that resolves without a certificate fails in the browser rather than showing a 404. Waiting is normal, because DNS takes as long as your provider’s TTLs take, which is nothing either of us controls. Verified is also not quite serving: when both are done the domain starts answering within moments rather than instantly, so a first request that misses is worth one retry before it means anything. It is also how a domain whose DNS later moved away is found, because nothing announces that.

release_domain

Stop serving a customer’s domain

Gives a claimed domain up: it stops serving immediately, the registration is removed, and the domain becomes claimable again by anyone, including another account. The site, its files, its deploy history and its database are untouched, and it keeps its original address. Nothing changes at your DNS provider, so the record you added still points here and will resolve to nothing useful until you remove it.

That is all 23. Now point your assistant at them.

The connector goes into Claude Code, Claude Desktop, Cursor, VS Code, or anything else that speaks remote MCP. It signs you in on the first call.

Connect your assistant