Connecting an assistant to juudd
Everything between an assistant with no tools and a site online: which client you are in, what happens at sign-in, and the rules the 18 tools enforce.
Written for the person setting it up. If you are an assistant reading this to connect yourself, the steps below are yours to run. The only thing you need a person for is the browser sign-in, and that happens on its own the first time you call a tool.
1 · Add the connector
Pick your client. Three of the six publish an installer, and those lead with it; the other three take the URL on the bar. Adding it is the whole of the setup: there is no API key, no token and no header to set by hand, because the server advertises its own authorization metadata.
claude plugin marketplace add https://juudd.com/marketplace.json
claude plugin install juudd@juuddThe plugin adds the connector above and the rules its tools enforce, in one step.
[mcp_servers.juudd]
url = "https://mcp.juudd.com/mcp"then codex mcp login juudd
{
"mcpServers": { "juudd": { "url": "https://mcp.juudd.com/mcp" } }
}{
"servers": { "juudd": { "type": "http", "url": "https://mcp.juudd.com/mcp" } }
}With GitHub Copilot
Settings → Connectors → Add custom connector, and paste the URL.
Needs a Pro, Max, Team or Enterprise plan. A config file will not work for these.
Add a remote MCP server over HTTP at the connector URL.
The server advertises its own authorization metadata, so there is no API key, no token and no header to set by hand.
2 · Sign in
Sign-in triggers on the first tool call rather than on connecting. Ask your assistant to call hello with any name. A browser window opens, you sign in once, and your client holds the token from then on.
hello answers with the account it is acting as. If it returns an account, you are connected.
What you now have
18 tools: deploy files and server code, create, protect and hand over a Postgres database, and point a domain you already own at the result. The reference page lists every one of them, what it does and what it refuses.
Some are absent rather than present-and-failing when a deployment has not configured what they need. Read the tool list your client actually shows you; do not assume a tool exists because a page named it.
The rules a deploy is refused by
Every rule below is a refusal the platform issues on purpose, and each one names its alternative in the same sentence. Reading them now is usually the difference between a deploy and three turns of guessing. They are the rules services/mcp enforces, not advice.
Server code
- No bare imports, with exactly one exception. Only import { neon } from '@neondatabase/serverless' is allowed. Every other bare specifier is refused: hono, express, anything from npm. There is no install step and no bundler, so an import that is not there at deploy time is not there at runtime either. Relative imports between your own files are fine. This is the rule most often broken, because it is the one the framework habit walks straight into.
- Use the Workers fetch shape, not the Pages one. It is export default { async fetch(request, env) { … } }. onRequest(context) is Pages' vocabulary and is refused by name. A bare export default async function (request, env) also works, but the object form is the documented one.
- Server functions are plain JavaScript. A .ts or .jsx file is refused by extension, before anything parses it. Write .js or .mjs.
- Functions live under functions/, and the path spells the route. functions/api/notes.js answers /api/notes. There is no catch-all: a /* route is refused.
Databases
- A table cannot be deployed without a row-level security policy. The platform reads the schema at deploy time and refuses a table that has none. Write the policy in the same apply_sql call that creates the table.
- Five kinds of SQL statement are refused before the transaction opens, by text match: DISABLE ROW LEVEL SECURITY, granting BYPASSRLS or SUPERUSER, GRANT/REVOKE naming the app role, DROP/ALTER ROLE on the app role, and ALTER DEFAULT PRIVILEGES. Each would expose a live table in the window between deploys, when the deploy-time check is not watching. An ordinary grant to an ordinary role is not refused. Only the app role is protected.
Ownership
- You can only act on sites your account owns. Every tool taking a site checks first. A name someone else holds is refused, and so is a site with stored history and no owner.
Secrets
- An API key never goes through the assistant. Deployed code is public, so a key does not belong in it; and a key in a tool call is a key in the transcript, so no tool takes one. The assistant calls request_secret with a name such as STRIPE_SECRET_KEY, you paste the value in the panel, and the site's functions read it as env.STRIPE_SECRET_KEY. The site has to have been deployed once first; after that every deploy carries the value forward without anyone reading it.
A deploy replaces a site, it does not patch one
read_site hands back the file list of a stored version and the contents of any text file in it. That is how a site built in one conversation gets changed in a later one, by an assistant that was not there when it was written: read what is there, edit it, deploy the whole set again.
A file left out of the next deploy_site is a file removed from the site.
What the platform will not do
5 things, each with its reason, because most are cheaper to work with than to work around, and one of them is a guarantee rather than a gap.
- It will not build a framework project. Server code is plain JavaScript, and the only import from npm that is allowed is the Neon driver. There is no install step and no bundler, so a Next.js or Vite project has nothing here to build it.
- It will not show what a function printed. There is no way to read what a function printed. site_errors says which routes failed, when and how many times, not why; what a request returns is still what you have to debug from, so write your functions to answer with the thing that went wrong.
- It will not delete anything. Every version you deploy stays in the store, which is what makes a rollback a publish and not a restore. It also means a site cannot be wiped clean, only replaced.
- It will not serve an apex domain everywhere. Your own domain works on the paid tier, and example.com with no www in front of it works only if your DNS provider flattens CNAMEs or offers ALIAS records. If yours does not, use www.example.com and redirect the bare name at your provider.
- It will not take a domain that is behind another CDN. Ownership of a domain is proven through its DNS, and another CDN in front of it hides exactly what has to be read. The domain has to come off that CDN first, so a site that must stay behind one is not a fit.
Two bounds, and the sentence each one refuses with
Neither is a limit on what a site can be; both are limits on how a site is sent. Both are refusals (recorded, readable, and worded to say what to do next), so meet them before they are hit rather than retrying into them. ADR-0044.
A bundle is bounded at 4 MB as sent, text and base64 counted together. Over it, deploy_site refuses with the size and the bound and changes nothing. Keep images small, or serve large assets from elsewhere by URL.
One account may call each tool only so often per minute. Over the bound, the refusal says how many seconds to wait. If something is looping, the loop is the problem and not the platform.
| Tool | Per minute | Why that one |
|---|---|---|
| deploy_site | 12 | An object write per file, plus a script upload. |
| publish_deploy | 12 | The same upload path, pointed at a version you already sent. |
| create_database | 6 | Each one is a Neon project, and it is idempotent per site. |
| apply_sql | 60 | A transaction against a live database. |
| set_database_url | 6 | Rebinds a live site to a different database. |
| claim_domain | 6 | Each one opens a certificate order. |
| reset_database_credential | 3 | Each one invalidates a credential a live site is using. |
| everything else | 240 | Reads are cheap, and reading a site back file by file is ordinary. |
Three things worth knowing before you debug
An spa bundle answers 200 for every path, including ones that do not exist. SPA routing serves index.html for anything the files do not match, which is what makes client-side routing work and which also means a typo, a dead link and a missing asset all come back 200 with the page shell. A missing JavaScript module therefore fails in the browser as a syntax error on < rather than as a 404, with the network tab showing success. The platform does not decide this and cannot: it is what an SPA is. If you want a real 404, static routing gives one, and a file named 404.html in the bundle is what makes that 404 a page rather than a blank body.
A domain takes a person and an unknown amount of time. The call claim_domain returns one DNS record to add at your registrar. No tool adds it and no tool can watch you adding it. Add the record, then call domain_status once, and do not poll in a loop: DNS takes as long as your provider's TTLs take, and "not ready yet" for hours is normal. Two things must both be true before a domain serves, that it points here and that a certificate exists for it, and domain_status reports them separately for a reason: a domain that resolves without a certificate fails in the browser with a security warning rather than a 404. How long a certificate takes has not been measured, so nothing here will tell you.
One thing to be careful with. The tool transfer_database mints a one-time URL that moves a database out of the platform permanently. Calling it is not a read: it marks the record as handed over immediately, before anyone opens the link. After that the platform reports the database as gone and cannot tell you whether anyone took it, and once the link is used it cannot read that database, back it up, or get it back. Call it when you have decided to take your database out, never to find out what would happen. database_status is the tool that answers questions.
When a call is refused
list_refusals answers with what your account has been told no about, newest first, in the exact words each refusal was given in. Read it before retrying something that failed in a conversation you were not part of: a deploy the schema gate stopped, a site another account owns, SQL against a database that has been handed over. Successes are not in it; list_deploys already shows those.
Where the rest of it is
- The 18 tools: every tool, what it does, and what it refuses.
- Support: what can be done about an account, what cannot, and where to write.
- Privacy and Terms: what is stored, what is kept permanently, and on what basis this is offered.
- Report abuse: for a juudd-hosted site that belongs to someone else.