跳到正文
DEV Community· CloudHike Team·· 4 小時前AI 評分54

CloudHike AI 摘要器建置教學:以單一 MCP 連線配置背景工作、重試、簽名 Webhook 和 Postgres

Build an AI summarizer with background jobs: retries, signed webhooks and Postgres over one MCP connection

AI 導讀

CloudHike Team 示範如何透過單一 MCP 連線,建立以背景工作執行模型呼叫的 AI 摘要器。API 將請求存入 Postgres 後立即回覆,背景工作呼叫 AI 模型,並在網絡錯誤、429 或 5xx 時重試。Webhook 會驗證簽名、按 run id 去重,再把摘要存入 Postgres。

正文

This is week 2 of Build with CloudHike, a weekly series where we build a small, real-world app with an AI coding agent and let the agent set up the backend. Disclosure: I built CloudHike, so keep that in mind.

Every AI feature starts the same way: one await on a model call inside an API route. It works on your laptop. Then real users paste long documents, the model takes its time, the provider answers 429 now and then, and your request sits there waiting while the browser spinner keeps spinning. A failed call means the user clicks again, and you pay for the same work twice.

The usual fix is a job queue: a queue service, a worker process somewhere to run it, retry logic, and a way to get the result back to your app. That's a small piece of infrastructure before you've improved anything for the user.

This week we'll build an AI summarizer that moves the slow model call into a CloudHike background job, with retries, a signed webhook for the result, and duplicate protection. As always, the agent sets up the backend over one MCP connection, and every prompt is below.

What we're building

A "paste text, get a summary" app:

  • A page with a big text box and a Summarize button.
  • An API route that answers at once. It saves the request in Postgres as queued and starts a background job run, so the user never waits on the model.
  • A worker route that the job calls. It does the slow part: one call to an AI model.
  • Retries on network errors, 429s and 5xx answers, handled by CloudHike instead of your code.
  • A webhook route that receives the finished run, checks the x-cloudhike-signature header, skips run ids it has already handled, and saves the summary.

Here's the flow:

  1. Browser → POST /api/summaries → a row in Postgres plus a new run, answered right away
  2. CloudHike Jobs → POST /api/jobs/summarize (your worker) → AI model → summary as JSON
  3. CloudHike Jobs → POST /api/jobs-webhook → signature check → duplicate check → summary saved
  4. The page polls its own GET /api/summaries/:id until the summary is there

And here's how each piece maps to CloudHike, all in one project:

  • Postgres 16 stores the summaries and the run ids we've handled (DATABASE_URL)
  • Background jobs call the worker, retry it and deliver the result (JOBS_API_URL, JOBS_API_KEY)
  • AI models write the summary through one OpenAI-compatible key (AI_BASE_URL, AI_API_KEY)

Normally that's a database host, a queue, a worker host and an AI provider account: four places to sign up and four sets of keys. Here it's one project, one .env and one credit balance. You don't need an OpenAI, Anthropic or Google account; you pick a model by name, such as openai/gpt-5-mini or anthropic/claude-sonnet-4.5.

What you need

  • A CloudHike account. Every new account gets $10 in free credits (eligible accounts, valid for 90 days), plus an extra $5 with the code TUBEGURUJI5, and no card is needed. The Free plan allows 2 projects, and background jobs are free on Free within its limits (500 runs a day).
  • Cursor or Claude Code (Codex, VS Code, Windsurf and Gemini CLI work too).
  • Node.js, for a Next.js app.
  • Somewhere public to run it, such as Vercel. The job worker calls public URLs only: localhost, private networks and cloud metadata addresses are refused. We'll deploy before the first real test.

Step 1: Connect CloudHike to your agent (once)

If you did week 1, you're already connected; skip to Step 2.

In the CloudHike dashboard, open Connect MCP in the sidebar, pick your editor and click Copy with key. That creates your workspace API key and gives you a snippet with the key already filled in. The server is remote (Streamable HTTP), so there's nothing to install.

Claude Code

claude mcp add --transport http --scope user cloudhike https://cloudhike.dev/api/mcp \
  --header "Authorization: Bearer icu_your_api_key"

Run /mcp inside Claude Code to check that it's connected.

Cursor (~/.cursor/mcp.json)

{
  "mcpServers": {
    "cloudhike": {
      "url": "https://cloudhike.dev/api/mcp",
      "headers": {
        "Authorization": "Bearer icu_your_api_key"
      }
    }
  }
}

Open Cursor Settings → MCP and look for the green dot. Snippets for the other editors are in the MCP docs.

Treat that key like a password. It lives in your home folder, not in the repo, and you can revoke it from the same page.

Step 2: Scaffold the app

npx create-next-app@latest summarizer
cd summarizer

Open the folder in Cursor, or run claude in it.

Step 3: Let the agent provision the backend

Paste this prompt:

Use the cloudhike MCP tools to set up the backend for this app. It's an AI summarizer that runs the model call in a background job.

Create a CloudHike project called "summarizer" and deploy postgres and jobs. Wait until they're ready. Put the .env into .env.local, make sure it's git-ignored, and don't show me any secret values. Then read the jobs, postgres and ai docs with get_docs so you know how to use them. Don't write app code yet. Just tell me which services are running and which variable names are in .env.local.

Behind the scenes, the agent works through CloudHike's MCP tools:

  1. create_project makes the project (or list_projects finds it if it already exists).
  2. deploy_services deploys Postgres and Jobs, with the same plan and billing checks as the dashboard.
  3. wait_for_services waits until each one reports ready.
  4. get_environment returns the project's .env, which the agent writes into .env.local. It can also ask for format: "names" to list just the variable names with no secrets.
  5. get_docs pulls the Jobs, Postgres and AI pages as Markdown, so the code it writes next follows the real API instead of a guess.

One click for the AI models. deploy_services covers Postgres, Auth, Storage, Redis, Jobs and Email, so the AI service is the one you deploy yourself: open the project in the dashboard and click Deploy on the AI card. Then ask:

Use the cloudhike get_environment tool again and update .env.local, keeping any keys I added. Show me only the variable names and confirm AI_BASE_URL and AI_API_KEY are there.

Step 4: The tables

Read https://cloudhike.dev/docs/md/postgres. Using node-postgres, add a migration script to this repo that creates two tables:

  1. summaries: id (uuid, default gen_random_uuid()), input_text, status ('queued', 'done' or 'failed'), summary (nullable), run_id (nullable, unique), error (nullable), created_at and updated_at.
  2. handled_runs: run_id (text primary key) and handled_at. Run the migration with DIRECT_URL if .env.local has it, otherwise DATABASE_URL. Create the app's pool once in lib/db.ts from DATABASE_URL, keep it on globalThis so hot reloads reuse it, and use max 5 connections. Then use the cloudhike describe_table tool to show me both tables.

handled_runs is our duplicate guard, and we'll come back to it in Step 7. The migration goes through DIRECT_URL because DATABASE_URL goes through a connection pooler (PgBouncer), and the Postgres docs recommend the direct address for migrations.

Same safety model as week 1: CloudHike's query_database tool always runs one statement in a READ ONLY transaction, execute_sql (for writes) is Pro-only and your editor asks before running it, and no MCP tool can delete a project or service. Schema changes live in your repo as migrations.

Step 5: The worker: where the slow AI call lives

This is the route CloudHike Jobs will call. It holds the slow part, so nothing user-facing waits on it.

Read https://cloudhike.dev/docs/md/jobs and https://cloudhike.dev/docs/md/ai. Add lib/jobs-signature.ts with a verify(rawBody, header) function based on the x-cloudhike-signature example in the jobs docs: HMAC-SHA256 of "." with JOBS_API_KEY, compared with timingSafeEqual, refusing timestamps more than 300 seconds old.

Then add POST /api/jobs/summarize:

  1. Read the raw body with request.text() and answer 401 if verify fails.
  2. Parse { summaryId } and load that row's input_text from summaries.
  3. Call the AI service with the OpenAI SDK, baseURL AI_BASE_URL and apiKey AI_API_KEY, model "openai/gpt-5-mini", asking for a short summary in plain text.
  4. Answer 200 with JSON { summary }.
  5. If the AI call fails with a 429 or 5xx, or the network fails, answer 503 so the job retries. For any other error, answer 422 with a short message, so the run fails without retrying. Server code only. Never send JOBS_API_KEY or AI_API_KEY to the browser.

A few details from the Jobs docs shape this route:

  • Every request CloudHike sends is signed, both to your job url and to your callbackUrl. The x-cloudhike-signature header looks like t=<unix seconds>,v1=<hex>, where v1 is an HMAC-SHA256 of <t>.<raw body> with your JOBS_API_KEY. That's why the worker verifies it: the route is public, and this is how it knows the call came from your job and not from a stranger.
  • Retries are by status code. A run is retried after a network error, a 429 or a 5xx, up to retries extra tries (0 to 5, default 3). A timeout or any other answer, such as 400, is final. So the worker decides: 503 means "try again", 422 means "don't bother".
  • A run succeeds on any 2xx, and CloudHike keeps the first 256 KB of what you answer, parsed as JSON when you answer JSON. That { summary } is exactly what the webhook will receive.

Step 6: Start the run (and answer at once)

Add POST /api/summaries. Validate that the text isn't empty and is at most 20,000 characters. Insert a summaries row with status 'queued'. Then, from server code, start a CloudHike job run: POST to JOBS_API_URL/runs with the x-api-key header set to JOBS_API_KEY and the body { url: APP_URL + "/api/jobs/summarize", method: "POST", body: { summaryId }, retries: 3, timeoutSeconds: 300, callbackUrl: APP_URL + "/api/jobs-webhook" }. APP_URL is a new env variable with the app's public address. Save the returned run id in the row's run_id and answer 202 with the summary id. If starting the run answers 429, mark the row failed with "Busy, try again in a minute".

Add GET /api/summaries/[id] that returns status, summary and error from Postgres. Then build the home page: a textarea, a Summarize button, and a result area that polls GET /api/summaries/[id] every 3 seconds until the status is done or failed.

Starting a run is one fetch. CloudHike answers at once with the run's id and status queued, then its worker calls your URL and waits for the answer, for up to 5 minutes on Free and 60 on Pro (timeoutSeconds goes up to 300 on Free and 3600 on Pro). Your API route is done in the time it takes to insert a row and make that one call.

We pass the summary id in the job body rather than the whole text, which keeps the job small (bodies can be up to 256 KB) and keeps the text in your own database.

Step 7: The webhook: verify, de-duplicate, save

Add POST /api/jobs-webhook, following the Next.js webhook example in the jobs docs:

  1. Read the raw body with request.text() and verify x-cloudhike-signature with lib/jobs-signature.ts before parsing anything. Answer 401 if it fails.
  2. Read the run id from the x-cloudhike-run-id header.
  3. In one transaction: INSERT the run id into handled_runs with ON CONFLICT DO NOTHING. If no row was inserted, we've already handled this run, so commit and answer 200 "already handled".
  4. Otherwise find the summaries row with that run_id. If the run's status is succeeded, save response.body.summary and set status 'done'. If it's failed or cancelled, set status 'failed' and save the run's error. Commit.
  5. If no summary has that run id, log it and answer 200. Answer 200 whenever the request was handled.

Two lines in the docs make this route worth the care:

  • Runs are delivered at least once. In rare cases, such as a worker restart mid-run, your URL can be called twice for the same run. The docs' answer is to use x-cloudhike-run-id to skip a run you've already handled, and a primary key on handled_runs does exactly that, without a race between two deliveries.
  • The webhook itself is retried. Answer with any 2xx; any other answer is retried 6 times over about 15 minutes. That's why a duplicate gets a 200 too: it tells CloudHike to stop, and the transaction makes sure a run is only marked handled once its summary is saved.

The webhook receives the same JSON you'd get by polling GET JOBS_API_URL/runs/<id>: status, attempts, error, durationMs and response.body. If you'd rather not pass callbackUrl on every run, set a webhook URL once under Jobs → Manage → Webhook in the dashboard. A run's own callbackUrl wins over it.

Step 8: Deploy to Vercel and run it

CloudHike doesn't host apps yet (app hosting is coming soon), so the Next.js app goes on Vercel and the backend stays on CloudHike. Because the job calls your worker and webhook over the internet, this is also where the app comes alive:

  1. Push the repo to GitHub. Check that .env.local is not in the commit.
  2. Import the repo in Vercel.
  3. Add the variables from .env.local under the project's Environment Variables. In the CloudHike dashboard, Environment → Copy as .env gives you the whole file. Add APP_URL with your Vercel production address.
  4. Deploy, open the app, paste a long article and click Summarize.

Want to test the webhook on its own first? Jobs → Manage → Webhook can send a signed test event to your URL. If it doesn't match a summary, your route logs it and answers 200, and either way you've seen the signature check pass.

To see what happened without leaving the editor, ask:

Use the cloudhike query_database tool to show the 10 newest summaries with their status, run_id and error, and how many rows are in handled_runs.

That runs read-only, so it's safe to let the agent explore. The Runs page in the dashboard shows the last 100 runs, and GET JOBS_API_URL/runs?limit=20&status=failed lists recent failures if you want an admin view.

A few production notes from the docs:

  • Jobs limits. On Free: 500 runs a day, 30 a minute, 5 in progress at once (more wait their turn), and a 5-minute timeout. On Pro: no daily limit, 600 a minute, 25 at once, a 60-minute timeout, and 50,000 run-seconds a month per project included, then $0.01 per 1,000 run-seconds. Run time is the time CloudHike waits on your URL, so each retried attempt counts. Over a limit, starting a run answers 429, which our route already handles.
  • Runs are kept for 7 days, results included. The summary you care about lives in your own Postgres.
  • Not everything should retry. If a job isn't safe to run twice (say, it charges a card), set retries: 0. A summary is safe to redo, so 3 retries is fine here.
  • AI limits. On Free, AI models allow 20 requests a minute and up to $2.00 a month on models; on Pro it's 300 a minute, within your credits and spend cap. Each request costs the provider's price plus 5%, and prompts and answers are never stored.
  • Your host's limits still apply. The job waits up to timeoutSeconds, but your worker route also has to be allowed to run that long on your host. Check its function duration setting.
  • Keep the database pool small (max: 5), since each serverless instance opens its own. And a Free database with no connections for 6 hours is stopped until you click Wake on the card; Pro databases are always on.

What CloudHike handled

Look back at what you didn't do: you didn't set up a queue, run a worker server, write retry loops, build a webhook signing scheme or open an AI provider account. One MCP connection, and one click on the AI card, gave the agent:

  • a project with Postgres and background jobs deployed and the .env written without showing secrets in chat,
  • a job runner that calls your URL, retries 429s, 5xx answers and network errors, and waits up to 5 minutes on Free or 60 on Pro,
  • signed requests to your worker and webhook, with a run id for de-duplication,
  • hundreds of AI models (GPT, Claude, Gemini, Grok, DeepSeek, Qwen and more) behind one OpenAI-compatible key, billed from the same credits,
  • the real docs for each service before writing code, and read-only tools to check the data.

Everything shows up in one dashboard and comes out of one credit balance. If you outgrow Free, Pro is $10/month with $10 of usage credits included every month. The details are on the plans and billing page.

Your turn

The whole build is the five prompts from Step 3 to Step 7, plus the env refresh and the optional query. Run them, swap the model, change the prompt to extract action items instead of summaries, and see how far your agent gets.

👉 Start free at cloudhike.dev. You get $10 in credits at signup, plus $5 more with code TUBEGURUJI5, no card required.

Next week: a file-sharing and image-upload app with Storage, Auth and Postgres. Users upload straight from the browser with presigned URLs, each user sees only their own files, and your agent can list the bucket without leaving the editor. Follow along so you don't miss it.

If you try this one, tell me in the comments what your agent built, or where it got stuck. I read every comment.

來源:DEV Community · dev.to