Skip to content

The connector

Give an agent this connector and it holds one MCP endpoint for one wawesome workspace. Through it the agent deploys a JavaScript Function and the files served beside it, runs it, attaches a custom domain and reads back the invocations that followed. Every call acts inside the workspace the presented credential belongs to, so no tool here takes a workspace as an argument.

MCP walks through adding it to Claude or to ChatGPT. This page is what the endpoint declares.

POST https://api.wawesome.io/v1/mcp

Streamable HTTP over HTTPS, and stateless. GET and DELETE on the endpoint answer 405. There is no stream to open and no session to delete, because a credential is presented on every request.

The endpoint declares 35 tools and shows every caller all of them. A tool the presented credential cannot run refuses and names the capability words it wanted, rather than disappearing from the list.

Each one declares a title, a readOnlyHint, a destructiveHint and an idempotentHint. openWorldHint is false on all 35. Nothing here reaches outside the workspace the credential names.

Tool What it does Capability readOnlyHint destructiveHint
whoami The workspace the credential acts inside, the words it carries and the Apps it reaches none true false
list_apps The Apps in the workspace, with the address each answers at read:apps true false
list_templates Every template the platform publishes none true false
get_template One template’s source and what it declares none true false
invoke_function Runs a deployed Function once write:runs false true
fetch_function Fetches one path from a Function’s live version, a long body in parts write:runs true false
rollback_function Puts an earlier version back on the public address write:functions false true
set_env_var Sets an App’s environment variable write:env false true
list_env_vars An App’s environment variables: each key, its kind and when it was set, and a variable’s value read:env true false
attach_domain Attaches a domain to an App and answers the DNS records to add write:domains false true
cancel_domain_claim Gives up a claim on a domain nobody has proved control of yet write:domains false true
check_domain Where an App’s domain got to, asked of the certificate vendor read:domains true false
list_functions The Functions in one App read:functions true false
list_versions One Function’s versions, newest first read:functions true false
get_function_source The code a version is running, and the files it carries read:functions true false
read_function_file One file a version or a draft carries, by path, a long file in parts read:functions true false
open_draft Opens a draft of a Function: a private copy of its files that nothing serves write:functions false false
list_drafts The open drafts of a Function, with who owns each one read:functions true false
get_draft_changes The paths a draft added, changed and deleted against its version, and its revision read:functions true false
edit_draft Adds, writes, changes and deletes a draft’s files in one step, all of it or none write:functions false false
preview_draft A link that shows a draft as it stands on every request, until the draft ends write:functions false false
discard_draft Ends a draft and every change in it write:functions false true
promote_draft Makes the draft revision the person approved live as a new version, and ends the draft write:functions false true
list_invocations One Function’s invocations, newest first read:invocations true false
get_invocation One invocation by id read:invocations true false
read_invocation_logs One invocation’s captured log body, a page at a time read:invocations true false
get_usage What this workspace spent this period against what its plan grants read:tenant true false
get_function_health Each Function’s requests, failures, 5xx answers and p95, for one App or every App, most failures first read:apps true false
get_export Whether the export is ready, and a download link once it is write:exports true false
diagnose_latest_failure The latest failed invocation, its log body and the version that ran it read:invocations, read:functions true false
deploy_function Deploys code, the files served beside it, private files, or all of them, and promotes what it made write:functions false true
request_upload A link a person opens to hand the agent files it cannot produce write:functions false false
list_uploads What a person sent against one upload session, with their instructions for the set and each image’s size read:functions true false
read_upload The contents of one uploaded text or JSON file, a page at a time read:functions true false
deploy_status How the deploy behind a handle went read:functions true false

Two rows want reading twice. fetch_function is read-only because it answers what a path served and changes nothing, and it asks for write:runs because serving that path runs the Function. request_upload is neither read-only nor destructive. It mints a link and then waits for a person to open it.

list_uploads answers instructions on the session: what the person wrote about the files as a set, or null. Each upload carries width and height in pixels. The server reads them from the stored bytes of a png, jpeg, gif, webp or avif, and they are null for any other type or for an image whose header does not parse. The upload page accepts every type the platform serves, including .csv, .tsv, .md and .ics. It refuses markup (.html, .htm, .svg, .xml) and unknown extensions. request_upload refuses a note that names a markup file, such as index.html, and tells the agent to send the file itself in the deploy or with edit_draft.

get_export runs only for the workspace’s owner, signed in as themselves. write:exports is never granted to a credential, so the connector sees the tool and is refused by it. No tool starts an export. Only a person starts one, from the dashboard or the CLI.

No tool reads a secret back. list_env_vars answers a variable’s value, but a secret only as its key, its kind and the date it was set. None of its value is returned, not even the last characters.

The endpoint answers a request carrying no token with 401 and an RFC 6750 challenge naming where to look next:

WWW-Authenticate: Bearer resource_metadata="https://api.wawesome.io/.well-known/oauth-protected-resource/v1/mcp", scope="..."

Discovery metadata sits at both standard locations. RFC 9728’s resource metadata answers on the API hostname, at /.well-known/oauth-protected-resource/v1/mcp, and names https://auth.wawesome.io as the authorization server. RFC 8414’s authorization server metadata answers there, at /.well-known/oauth-authorization-server. A client that predates RFC 9728 and asks for that document on the resource’s own origin, at https://api.wawesome.io/.well-known/oauth-authorization-server/v1/mcp, is redirected to it.

A client identifies itself one of two ways. Neither one takes a client secret, and neither takes a configuration step.

  • A Client ID Metadata Document. The client’s client_id is an HTTPS URL, and the server fetches its metadata from that address. Every redirect URI the document declares must be on that same origin, or plain HTTP on loopback for a native client. The authorization server metadata declares "client_id_metadata_document_supported": true.
  • Dynamic client registration per RFC 7591, at https://auth.wawesome.io/oauth/register, named in the metadata as registration_endpoint.

A redirect URI is matched against the ones the client registered, byte for byte. The one exception is a native client listening on loopback. A client that registered http://localhost/callback or http://127.0.0.1/callback may be answered on any port at that same host, which is what RFC 8252 asks for: the operating system hands it the port, so it cannot register it in advance. The scheme, the host, the path and the query all still have to match exactly, and the port it authorized on is the port it has to present when it exchanges the code.

Proof of possession is PKCE. code_challenge_methods_supported is ["S256"] and nothing else. plain is neither offered nor negotiated down to, and an authorize request carrying no challenge is refused.

Codes become tokens at POST https://auth.wawesome.io/oauth/token, as application/x-www-form-urlencoded, over both the authorization_code and refresh_token grants. The answer is Cache-Control: no-store. An authorization code is worth five minutes and an access token an hour.

Refreshing rotates the refresh token. The rotation replaces the grant’s secret where it stands rather than minting a second credential, so a connector running daily for a year stays one row in the workspace with its last-used date moving. A grant nobody has presented for 90 days is over, counted from its last call rather than from the day it was issued.

The token a rotation replaced still works for 60 seconds, so two of a connector’s requests crossing the expiry at the same moment both refresh instead of one of them ending the install. Nothing older than that one token is ever accepted, and neither is that one a minute later: a refresh token from further back is a copy of it in somebody else’s hands, so presenting it ends the grant rather than refreshing it, and the connector has to be installed again.

The metadata’s scopes_supported is our own capability words, read off the same table a credential is cut from. What a connector ends up holding is what one person approved on one screen, and they can hold it to named Apps.

It reads the Apps in one workspace and the addresses they answer at, their Functions, their versions and the code a version is running. It reads invocations with their outcome and what they spent, and the log bodies captured with them. It reads where a custom domain claim got to, and what the workspace has spent against its plan.

It writes new versions of a Function and the files served beside them, environment variable values, custom domain claims, and runs of a Function.

It reaches no workspace but the one the credential belongs to.

An invocation’s captured log body is kept 14 days. The stored objects expire at that age.

The invocation’s own row, carrying its status, timing, fuel, peak memory and egress bytes, is kept 35 days, and a housekeeping pass then deletes it. That row records no request method, no path and no caller IP.

Deployed code and files last as long as a rollback can reach them. Ten versions behind the live one keep their files, and the bytes nothing references any more are reclaimed a day later.

The grant is one credential row, and it lasts until somebody revokes it or 90 days pass with no call.

A person’s name and email address come from the identity provider they signed in with. The connector neither collects them nor reads them back.

Revoke the connector on the dashboard, under Settings and then Credentials, on the row marked Connector. Its very next call is refused.

Delete an App from its own page on the dashboard. Its Functions, their versions and their files go with it.

The privacy policy says what else you may ask for, including a copy of what we hold about you and its erasure, and that we answer within a month.