wyrd/tools
esc

    ↑↓ Move ↵ Open t3c results first Search by Pagefind
    Releases

    Updates, T3 Connect, guests and MCP OAuth

    Updates, T3 Connect, guests and MCP OAuth

    Update a server, link it to a relay and use the routes that take no session.

    Server update and T3 Connect

    server.updateServer asks a server to move to an exact version of t3, and server.updateServerWithProgress reports its downloading and installing stages. Only a server under a service launcher or a desktop app can do this. Another server refuses with a ServerSelfUpdateError. server.commitDesktopUpdate lets a desktop host restart into an update that its app downloaded. Each needs environment:maintain.

    cloud.getRelayClientStatus reports the tunnel client that T3 Connect needs, and cloud.installRelayClient downloads it into the server’s home and reports each stage. A failure is a RelayClientInstallFailedError, and its reason names the cause.

    The connect.* routes read and change the server’s link to a relay. schemas.cloudLink holds their shapes, and schemas.httpErrors.EnvironmentHttpCloudError their failures:

    const state = await client.routes.call("connect.linkState");
    const reachability = await client.routes.call("connect.checkReachability");
    await client.routes.call("connect.preferences", { body: { publishAgentActivity: true } });

    connect.linkProof, connect.relayConfig and connect.unlink make and remove a link. A proof is only for the server’s own loopback origin, and relayConfig takes only an https relay. connect.health and connect.mintCredential are what a relay calls. They take no session, because the server checks the proof, which the relay signs with the key from relayConfig. A proof works once. The reads need relay:read, and the writes relay:write.

    Guests and webhooks

    guest.* are the routes of a person or program that has a share’s link from guestShares.create. They send no session token. guest.redeem answers with a cookie for that share, so give the client a fetch that keeps it, as for auth.browserSession. The credential is the link’s fragment. schemas.orchestrationGuestShare and schemas.orchestrationGuestStream hold their shapes.

    const guest = T3Client.create({ baseUrl, fetch: fetchWithCookieJar });
    const query = { share: shareId };
    await guest.routes.call("guest.redeem", { body: { credential, name: "Sample guest" } });
    await guest.routes.call("guest.submit", { query, body: { shareId, text: "Sample proposal." } });
    for await (const event of guest.routes.stream("guest.stream", { query }, signal)) {
      if (event.kind === "item") console.log(event.value);
    }

    routes.stream reads a route that answers with server-sent events: guest.stream and guest.commandOutput. An event that does not decode is a decode-error item, and the stream continues. A guest’s refusal is a T3HttpError whose reason names the cause, such as inactive when the link is closed or unavailable when the thread does not show the item.

    webhooks.webhookPost, webhookPut, webhookPatch and webhookGet call a webhook task’s endpoint. The path holds the token, so no error names it, and webhookUrlParams reads the params from scheduledTasks.webhookEndpoint. The server reads the body as raw bytes:

    const params = webhookUrlParams(endpoint.path);
    const { deliveryId } = await client.routes.call("webhooks.webhookPost", {
      params,
      headers: { "content-type": "application/json", "x-sample-signature": signature },
      body: new TextEncoder().encode(JSON.stringify({ action: "sample" })),
    });

    MCP OAuth

    mcpOAuth.* are the routes of an MCP client that gets a token for the server’s /mcp endpoint. They send no session token. schemas.authMcp holds their shapes. The library does not make the PKCE values and does not open a browser: the caller owns both.

    const client = T3Client.create({ baseUrl });
    const { client_id } = await client.routes.call("mcpOAuth.register", {
      body: { client_name: "Sample client", redirect_uris: [redirectUri] },
    });
    const authorization = {
      response_type: "code",
      client_id,
      redirect_uri: redirectUri,
      code_challenge: challenge,
      code_challenge_method: "S256",
      state,
    };
    const { location } = await client.routes.call("mcpOAuth.authorize", { query: authorization });

    mcpOAuth.authorize does not follow its redirect. location is a relative address of the server’s approval page, or the client’s redirect_uri with an error. A browser’s fetch hides a redirect, so there the route raises T3PreconditionError: send the browser to the URL instead. A client or a redirect that the server does not know is a T3HttpError whose body is an HTML page.

    mcpOAuth.approval and mcpOAuth.decision are what that page calls. A pairing-code decision takes a credential from auth.createPairingCredential with the scopes of the access. A server with the mcpApprovalCodes capability also takes an approval code: create the credential with access in place of scopes. The code, such as AUTO-…, approves one sign-in, and the decision must name that level; another level is refused with HTTP 400. The result’s access repeats the level. A browser-session decision takes the csrfToken from mcpOAuth.approval. Both routes send the client’s access token when it has one, and the server approves for a session with access:write and orchestration:read. A client that approves with a browser session’s cookie instead needs a fetch that keeps the cookie, as for auth.browserSession. The answer’s redirectTo holds a one-use code that lasts one minute:

    const { redirectTo } = await client.routes.call("mcpOAuth.decision", {
      body: { authorization, decision: { _tag: "pairing-code", access: "auto", code: credential } },
    });
    const token = await client.routes.call("mcpOAuth.token", {
      body: {
        grant_type: "authorization_code",
        code: new URL(redirectTo).searchParams.get("code") ?? "",
        redirect_uri: redirectUri,
        client_id,
        code_verifier: verifier,
      },
    });

    A refused register or token is a T3HttpError whose reason is the OAuth error, such as invalid_redirect_uri or invalid_grant. A refused approval or decision has the tag AuthMcpApprovalError.