Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

LoopMessage — Claude agent example

iMessage Conversation API — REST send + inbound webhooks.

Same shape as every example in this repo (see the root README): a Claude brain with per-conversation memory, a provider-agnostic message loop, a zero-credential terminal provider for local testing, and the real LoopMessage provider (src/providers/loopmessage.ts) wired to LoopMessage's API.

Run

bun install
cp .env.example .env       # add ANTHROPIC_API_KEY (terminal mode needs nothing else)

bun run dev                # terminal mode — chat with the agent locally, zero credentials
bun run start              # PROVIDER=loopmessage: starts the webhook server + REST sender

Agentcard for companies (per-user virtual cards)

This example also integrates Agentcard for companies: a connected user can ask the agent to issue/inspect their own Agentcard virtual cards. Cards are isolated per (this app, that user) — never a shared account.

Three parts, in src/agentcard/:

  1. Client (Part 1) — at startup the app self-registers a public OAuth client via Dynamic Client Registration (no admin, no secret). runtime.ts uses a pinned AGENTCARD_OAUTH_CLIENT_ID if you set one, otherwise it registers a fresh DCR client.
  2. User auth (Part 2) — OAuth 2.1 + PKCE. connect-server.ts runs a small HTTP server (GET /connect?space=<id> → Agentcard /authorize; GET /callback → token exchange) in the same process as the brain, so it shares the token store (store.ts, persisted to ~/.agentcard-loopmessage-tokens.json).
  3. MCP (Part 3)brain.ts opens an @ai-sdk/mcp client with the user's bearer token and exposes a curated card toolset (create_card, list_cards, get_card_details, check_balance, close_card, approve_request, list_transactions, list_all_transactions, get_planbuy is omitted).

When a conversation has no token the brain is plain chat; if the user asks about cards it hands them their /connect?space=<id> link. When a token is present the model can create/inspect cards in a multi-step loop (new cards default to $1.00; the card-creation approval gate is auto-resolved).

Env var Required for cards Notes
AGENTCARD_PUBLIC_URL yes publicly reachable base URL (a tunnel in dev); callback is <url>/callback
AGENTCARD_CONNECT_PORT no connect/callback server port (default 8912)
AGENTCARD_OAUTH_CLIENT_ID no pin a client instead of self-registering (DCR)
AGENTCARD_OAUTH_CLIENT_SECRET no only with a pinned confidential client id

Live terminal test (cards end to end)

export ANTHROPIC_API_KEY=$(cat ~/.anthropic_key)
cloudflared tunnel --url http://localhost:8912          # prints an https URL
export AGENTCARD_PUBLIC_URL=https://<printed-tunnel>.trycloudflare.com
bun run dev                                             # terminal mode

Then in the chat: type "make me a card" → the agent replies with a /connect?space=terminal link → open it, log into your Agentcard account → the callback prints ✓ connected space=terminal → back in the terminal type "make me a $1 card" and the agent issues it and reports the last 4 digits.

Test

bun test                   # offline: loop e2e + provider plumbing + Agentcard (PKCE/token/MCP)
RUN_LIVE=1 bun test        # also hits the real Anthropic API (needs ANTHROPIC_API_KEY)
  • test/e2e.test.ts — drives the full loop with a scripted provider + a fake Anthropic client; asserts the round-trip and per-conversation memory.
  • test/provider.test.ts — stands up a mock LoopMessage endpoint, POSTs a real-shaped inbound webhook to the provider, and asserts the reply is sent back out. Proves receive → brain → send without any real credentials.
  • test/agentcard.test.ts — offline Agentcard suite: PKCE math, token exchange/refresh against a mock /token (incl. confidential client_secret
    • refresh-token rotation), and the MCP flow (initialize → tools/list → tools/call with the approval-required → approve_request gate) driven through the real @ai-sdk/mcp client against a mock JSON-RPC MCP server, asserting the user's bearer token reaches the server.
  • test/live.e2e.test.ts — gated by RUN_LIVE=1; real Claude round-trip.

Real LoopMessage integration

src/providers/loopmessage.ts implements LoopMessage's documented contract (send + inbound webhook). Going fully live needs a LoopMessage account and the credentials below, plus a public webhook URL pointed at this server — exactly the same "terminal verified, live needs creds" status as the Photon sandbox.

Env var Required Notes
LOOPMESSAGE_AUTH_KEY yes Authorization key
LOOPMESSAGE_SECRET_KEY no Loop-Secret-Key (legacy accounts only)
LOOPMESSAGE_SENDER_NAME no sender name id (required to start a new convo)
PORT no webhook server port (default 8788)

Docs: https://docs.loopmessage.com/