Wicket

io.github.Stixels由 GitHub 帳號 Stixels 發布v1.0.0更新於 Nov 14, 2026

Search a product's docs, file bugs and feature requests, and hear back when they ship.

已驗證Streamable HTTP可網頁執行效率與工作流知識與記憶

概覽

AI 產生的概覽

Wicket 的 MCP 伺服器讓助理搜尋產品文件、提交錯誤或功能請求,並查看這些回報的處理進度。

可以做什麼
  • 搜尋文件索引,並讀取搜尋結果背後的完整內文,附上摘要與連結
  • 提交錯誤或功能請求,送出前會做重複檢查並回傳可能已存在的工單
  • 查看本人回報的狀態(待處理、已規劃、已修復、已婉拒),以及工單和團隊回覆
適用情境
適合團隊已用 Wicket 提供應用程式內協助,並希望 Claude Code、Claude Desktop 或 Cursor 中的助理依同一份文件作答、直接提交或追蹤回報的情況。
執行需求
透過 streamable HTTP 連線到 wicketchat.com 的遠端端點,需在 Authorization 標頭提供 bearer 權杖。示範權杖需從 Wicket 網站取得,有效期為一天。自架執行個體需開啟 MCP 伺服器並設定身分密鑰。
安裝前請注意
Authorization 中的 bearer 權杖屬於憑證,示範權杖一天後失效。提交回報會寫入團隊的工單系統,首次呼叫可能只回傳疑似重複的工單而不送出。回報與工單狀態受個人設定規則限制,結果取決於伺服器設定。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Wicket,將其新增到工作區。
  2. 連接 Authorization token 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
  3. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/io.github.Stixels%2Fwicket/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。

{
  "mcpServers": {
    "wicket": {
      "type": "http",
      "url": "https://wicketchat.com/mcp",
      "headers": {
        "Authorization": "Bearer <YOUR_TOKEN>"
      }
    }
  }
}

README

Wicket

A drop-in help and feedback chat for any web app, plus a Discord community and support inbox behind it. Users write one message. An assistant answers from your docs, and every conversation shows up in your team's Discord, where a reply goes straight back to the user in the app.

  • One script tag adds a chat button to your app. It captures the page, app version and recent console errors, so reports arrive with context.

  • Answers from your docs. It indexes any GitHub repo of Markdown or MDX (GitBook Git Sync, Docusaurus, Mintlify, ...), or any hosted docs site by its sitemap, and links the exact section.

  • Discord as the inbox and community. Each in-app conversation becomes a private forum post that your team answers from Discord. The same assistant can also answer new questions in your public #help forum.

  • Hands off to a person when the user asks, is frustrated, or the docs don't cover it, and pings a role you choose.

  • Cheap to run. One Node process with SQLite. The assistant runs through the Vercel AI Gateway or Anthropic directly, and it's optional: without a key, every chat goes straight to the team.

  • Works from AI tools. Wicket is also an MCP server, so people can search your docs, file bugs and check on them from Claude Code, Claude Desktop or Cursor. See MCP server.

  • Everyone hears back. With a tracker set up, the assistant logs bugs, ideas and docs gaps itself. A repeat report becomes another report on the ticket that already exists, not a new one. Everyone who reported it gets a message in their chat (and by email if they're away) when it's fixed, and for ideas, when the team plans it or decides not to.

Run the demo (no keys needed)

bash
npm installnpm run demo        # open http://localhost:4317

The demo indexes the Escape Director docs, uses a scripted assistant and simulates Discord. To simulate a team reply, run curl -XPOST localhost:4317/__staff -H 'content-type: application/json' -d '{"body":"Hi!"}'.

Add it to your app

html
<script src="https://YOUR-SERVER/widget.js" defer></script><script>  window.addEventListener("load", () =>    Wicket.init({      user: { id: currentUser.id, name: currentUser.name, email: currentUser.email },      context: { appVersion: APP_VERSION, venue: currentVenue.name },      title: "Escape Director Support",      theme: { accentColor: "#e8590c" },    }),  );</script>

You can also call Wicket.open(), Wicket.close(), Wicket.toggle(), Wicket.ask("question") (opens the chat and sends it), Wicket.setContext({ roomId }), Wicket.setUser({...}), Wicket.setUserToken(...), Wicket.setTheme({...}), Wicket.setAnchor(element), Wicket.setQuiet(true), Wicket.addBreadcrumb("Paused game") and Wicket.destroy() (removes the widget, for example on sign-out; init brings it back) at any time. The latest context and a fresh userToken go with every message.

Recent activity for bug reports. The widget keeps a short trail of what the person did in your app: the last 20 steps from the last 5 minutes, made of page changes (path only, never the query string), clicks on buttons and links by their visible label or aria-label, and console errors. It never records what anyone types or the value of a field. Add your own steps with Wicket.addBreadcrumb(label), for example "Paused game" or "Sent Clue"; it works before init. The trail stays in the page and is sent only with a bug report: with the first message after "Report a bug", or right after the assistant logs a bug on its own (then it's added to that ticket as a comment). The server caps it, strips anything that looks like an email, card number or token, and puts it in the model's transcript and on the ticket under ## Recent activity, nowhere else. Turn it off with Wicket.init({ trail: false }).

Your own button

To open the chat from a button in your app's header, hide the launcher and pass the button as anchor:

js
const button = document.querySelector("#help-chat");Wicket.init({ hideLauncher: true, anchor: button });button.addEventListener("click", () => Wicket.toggle());const off = Wicket.on("unread", (count) => (button.dataset.count = count || ""));
  • The panel opens just below the anchor, right-aligned to it, floating over the page. On phones it opens full screen as usual. If the anchor is gone or hidden, the panel uses the bottom corner.
  • When a reply arrives while the chat is closed, a preview drops from under the anchor (or sits above the launcher) with the reply, a one-line reply box and Open chat. It never plays a sound.
  • quiet: true (or Wicket.setQuiet(true)) turns the previews off, for screens other people can see; unread still fires so you can show the count yourself.
  • Wicket.on(event, callback) returns a function that unsubscribes. Events: unread (the count, on every change, including back to 0 when the chat opens), open, close, and rating ({ conversationId, messageId, rating } when someone rates an answer; rating is "up", "down" or null when they take it back). You can subscribe before init.

Each assistant answer has thumbs up and down on its name line. A thumbs down offers to pass the conversation to the team. Answers can come with How we got this (the numbers behind them) and Ask next questions under the latest one. When the server asks for a bot check, visitors pass a Cloudflare Turnstile check in the panel before their first answer.

Signed-in users

A name or id passed in user is only a label. For profiles that depend on who someone is (signed in, their plan) and for per-person budgets, your backend signs a short-lived token and the widget passes it on:

ts
// Your backend, with the same IDENTITY_SECRET Wicket has (HS256 JWT)jwt.sign({ sub: user.id, name: user.name, email: user.email, plan: user.plan }, process.env.WICKET_IDENTITY_SECRET, { expiresIn: "5m" });
js
Wicket.init({ userToken: () => fetch("/api/wicket-token").then((r) => r.json()).then((d) => d.token) });

userToken takes the token or a function that returns one. Without a valid token, the person counts as a visitor. Tokens must have an expiry (exp) no more than 24 hours out; a few minutes is best. The widget asks for a fresh one with every message.

Optional claims:

ClaimWhat it does
accountBill per account instead of per person: everyone with the same account spends from one budget.
budgetThe account's (or person's) monthly AI allowance in dollars. When a profile also has a budget, the lower one applies, so your app stays in charge of the amount.
envWhich of your environments signed it (staging, production), when one Wicket serves several. Picks the app-tools URL.

Deleting someone's data

When someone deletes their account in your app, your backend signs a token with scope: "erase" and posts it to /api/erase:

ts
const token = jwt.sign({ sub: user.id, account: user.accountId, scope: "erase" }, process.env.WICKET_IDENTITY_SECRET, { expiresIn: "5m" });await fetch("https://YOUR-SERVER/api/erase", { method: "POST", headers: { Authorization: `Bearer ${token}` } });

Wicket deletes every conversation the person started, and with account, every conversation billed to that account. That covers messages, files, the assistant's transcript and the team's Discord post. What each answer cost stays, without any words, so budgets add up. The answer is { deleted, failed }. A 502 means some Discord posts couldn't be removed yet; those conversations are kept, so call again later until you get a 200. Calling again is always safe. Only your backend should sign these tokens: an erase token never signs anyone in, and a widget's sign-in token can't erase.

App tools

Let the assistant read the signed-in person's own data, such as their results or settings, through tools your backend offers. Wicket calls them with the person's token, so your backend checks who they are on every call and Wicket never sees data they couldn't see in your app.

yaml
profiles:  default:    appTools:      url: https://api.example.com/wicket/tools   # or { production: ..., staging: ... } keyed by the token's env
  • GET {url}?context=<json> returns { instructions, tools: [{ name, description, inputSchema }] }. instructions are your rules for using the tools; they go into the assistant's prompt. inputSchema is JSON Schema.
  • POST {url}/{name} with { input, context, evidenceStart } returns { content, evidence? }. content is what the model reads. evidence items ({ id, label, value, period?, basis?, caveats? }) show folded under the answer as "How we got this", numbered from evidenceStart + 1 so ids stay unique within an answer. Answer with a 4xx and { error } for a problem the model should hear about.
  • context is what the widget last sent with Wicket.setContext(), for example the page, the record on screen and the date range. Wicket sends it to the model again whenever it changes.

Profiles with app tools get more steps per answer (AGENT_MAX_STEPS_WITH_APP_TOOLS, 20 by default).

Customizing

OptionDefaultWhat it does
title, subtitle"Help & feedback"Header text.
welcomeTitle, welcomeSubtitle"Hi {first name} 👋", "How can we help you today?"Large heading on the start screen.
greetingshort defaultLine under the start screen heading.
suggestions3 quick startsButtons shown before the first message. Pass [] to hide them.
placeholder"Write a message…"Text in the empty input.
logoUrlanimated botSquare image for the header and the assistant's avatar. Without one, the assistant is a small animated orb in your brand color that blinks, looks around and shows bouncing dots while it thinks.
launcherLabelnoneText next to the launcher icon, e.g. "Help".
hideLauncherfalseHide the floating button and open the panel from your own button.
showBrandingtrueThe small "Powered by" line. It links to your Wicket server's home page, which is the Wicket marketing page unless SITE=false.
theme.accentColor#4f46e5Brand color. Text on it switches between white and dark automatically.
theme.mode"auto""light", "dark", or follow the visitor's system.
theme.radius20Corner radius in px.
theme.fontFamilysystem fontAny CSS font stack your page loads.
theme.position"right""left" or "right".
theme.offset{ x: 20, y: 20 }Distance from the screen edges.
theme.zIndexvery highStacking order.
theme.cssVarsnoneOverride any internal variable, e.g. { "--wk-bg": "#fffaf5" }.
--wk-user, --wk-user-text, --wk-user-borderaccent, text on accent, noneColors of the person's own messages, set through theme.cssVars.

The widget renders in a shadow DOM, so your app's CSS can't break it and its CSS can't leak into your app. On phones it opens full screen.

Earlier conversations show under Recent on the start screen, and the clock button in the header lists all of them. The browser keeps each conversation's access token in localStorage (up to 50). For signed-in users (a verified userToken), the server also returns their conversations by user id, so their history, including reports and their progress, follows them to any device or browser. Visitors' history stays in the browser they used.

Set up Discord (community + support inbox)

  1. Go to https://discord.com/developers/applications and create a New Application. Under Bot, copy the token and turn on Message Content Intent.

  2. Under OAuth2 → URL Generator, pick the bot scope with these permissions: Manage Roles, Manage Channels, Move Members, View Channels, Send Messages, Send Messages in Threads, Create Public Threads, Manage Threads, Read Message History, Add Reactions, Embed Links and Manage Messages (to pin the welcome post). Manage Server is optional: with it the bot also points join messages and the Community welcome screen at #welcome. On a Community server it also keeps the rules channel (#📜・rules) in Start here with a pinned rules post, makes #announcements a real announcement channel, and moves Discord's admin notices channel into Team. Open the URL and add the bot to your server.

  3. Turn on Developer Mode (User Settings → Advanced). Right-click your server and choose Copy Server ID.

  4. Set DISCORD_BOT_TOKEN and DISCORD_GUILD_ID in the service variables. On every boot the service makes sure the server has a Team role and these channels, creating only what's missing:

    CategoryChannelsWho sees it
    Start here#👋・welcome, #📣・announcementsEveryone; only the team posts
    Support#❓・help (forum), 🎧 Talk to support (voice)Everyone
    Community#💬・general, #💡・ideas (forum), #📸・show-and-tell, ➕ Start a call (voice)Everyone
    Team#support-inbox (forum), #team-chatTeam role only

    Channels are matched by name with or without the emoji, and nothing is ever deleted. An existing channel keeps its place and settings; the bot only adds the emoji while it still has the plain name, fills in an empty topic, and adds forum tags and the 👍 default reaction on #ideas when they're missing. It keeps Start here at the top so new members land in #welcome, and keeps a pinned welcome post there with the house rules and where to go. Anything it can't do itself is logged as discord: needs you: …. To use an existing staff role, set DISCORD_TEAM_ROLE to its name. You can also run it by hand with npm run discord:setup.

  5. Give your teammates the Team role.

How it behaves:

  • In-app conversations become posts in #support-inbox. The user's messages show as blue cards and the assistant's as gray cards, so your own replies (plain messages) stand out. While the assistant is working the bot shows as typing. Reply in a post to answer the user in the app. The assistant stops replying once someone from the team does. When the assistant asks for a person, it pings the Team role and tags the post Needs a person. Only a reply that fails to reach the app gets a ⚠️ reaction.
  • When you're done, close the post (or tag it Resolved; deleting it works too, though the transcript is then gone from Discord). The user sees "resolved" in the widget, the post is tagged Resolved, and if they write again the post reopens and the assistant takes the new question first. Posts Discord archives on its own after inactivity aren't treated as resolved.
  • Tickets: with LINEAR_API_KEY and LINEAR_TEAM_KEY set, anyone with the Team role can tag the bot anywhere in the server, e.g. @Wicket file this as a bug in a support-inbox post. It reads the conversation, writes the ticket (summary, steps, context, who reported it), files it in Linear (Triage if the team has it on, otherwise Backlog) with the matching Bug/Feature/Improvement label, and replies with the link. Messages that tag the bot are never sent to the user. To file GitHub issues instead, set GITHUB_ISSUES_REPO and GITHUB_ISSUES_TOKEN (a fine-grained token with Issues: read and write); issues get the repo's bug, enhancement, documentation or question label when it exists, plus GITHUB_ISSUES_LABEL if you set one. The tracker is an adapter, so Jira can plug in the same way.
    • Tag the bot with a ticket key or link (@Wicket +1 ED-123, @Wicket same as <link>) to add the conversation to that ticket instead of filing a new one.
    • Tickets filed or added from a support-inbox post are linked to that app conversation, so its reporter hears back too.
  • Hearing back: Wicket asks the tracker every 15 minutes (tickets.checkMinutes) about tickets someone is waiting on. When the assistant logs a report, the person sees a slim status row in their own words. Bugs go from Logged to Fixed. Ideas go from Logged to Planned (the team moved it out of Triage) to Shipped, or to Not planned when the team cancels it. To tell people why, comment on the issue starting with Reply: (for example Reply: It would distract players mid-game.); Wicket waits an hour after a cancel so there's time to write it, and sends the newest one. A bug canceled with a Reply: comment tells its reporters too; one canceled without a word sends nothing. A ticket closed as a duplicate passes its reporters to the one it duplicates. With GitHub Issues there's no Triage, so ideas skip Planned; closing as completed counts as done and closing as not planned as declined. The check is plain tracker API calls, with no model calls. The dashboard lists each conversation's tickets. Turn off assistant filing with tickets.assistant: false.
  • Voice: joining ➕ Start a call creates a voice room named after you and moves you in. It's deleted when the last person leaves. 🎧 Talk to support works the same way, but the room is visible only to the caller and the Team role, and it pings the team. The bot needs the Move Members permission for both.
  • Announcements: set announcements.feed in config/wicket.yaml to an RSS feed, such as your product's changelog, and each new item is posted to #📣・announcements with its title, link, summary and date. On a Community server the post is also published, so servers that follow the channel get it. Wicket checks every 30 minutes (announcements.checkMinutes). The first check only remembers what the feed already holds, so nothing old is posted; set announcements.latest: 1 to post the newest item once so the channel doesn't start empty. To post somewhere else, set DISCORD_ANNOUNCEMENTS_CHANNEL_ID.
  • Community questions: with assistant.helpForum: true, when someone opens a post in #help, the assistant answers once from the docs. If it can't, it pings the team. Everyone else can keep the thread going. It's off by default, because Discord members aren't signed in to your app, so those answers can't count toward anyone's budget.

Team dashboard

A reply inbox at /admin, for teammates who'd rather not answer from Discord. It's another place to reply, not a replacement: Wicket keeps the record, so a reply from the dashboard reaches the user's widget and shows up in the Discord post, and a reply in Discord shows up in the dashboard.

  • Shared conversations. Nobody owns a conversation. The dashboard shows who has replied ("Alex and you replied"), and anyone can jump in. Teammates are linked across tools by their Discord account, so Alex replying in Discord and Alex in the dashboard are the same person.
  • Online status. A dot on the user's avatar shows whether they're around: solid green while the chat is open in front of them, a green ring while they're on the site with the chat closed or in a background tab. The conversation header says "In the chat now", "On the site" or "Last seen 12m ago", so you know whether to expect a quick back-and-forth.
  • Typing hints. While someone types in the dashboard or in the Discord post, the others see "Alex is typing" on the conversation, so two people don't write the same answer.
  • Hand off with a note. Switch the composer to Note (or type @) to leave a team-only note like "@Alex can you take this?". Notes never reach the user. In Discord the note pings Alex, and the dashboard lists it under Mentions until Alex writes in that conversation. In Discord, a message that starts with an @mention of a teammate or role is treated the same way: it stays with the team instead of going to the user, and gets a 📝 reaction.
  • Desktop notifications. Turn them on from the banner or your profile menu. You're notified when a conversation is passed to the team, when the user writes in a conversation the team has, and when a note mentions you, but not while you're looking at the dashboard. The tab title counts what you missed.
  • Saved replies. Answers the team gives often, shared by everyone: the ⚡ button in the composer, or type / in an empty reply to search them. {name} becomes the person's first name. "Save what I wrote" turns a reply you just wrote into one.
  • Views: Open, Needs reply (the user is waiting on the team), Mentions, Resolved, All. Resolve works like closing the Discord post. Each conversation shows the person, their plan, the page and app version they wrote from, and recent console errors.
  • Attachments are up to 10 MB each (Discord's limit without server boosts), 30 per conversation, and stored on the server's volume. A teammate can delete one from the dashboard (hover it, then the bin); it's removed for everyone and the message keeps its name, marked removed. Files go automatically after files.keepDays (90 by default, 0 keeps them) and uploads that were never sent go after a day. Copies already posted to Discord stay there.

Sign-in is with Discord, for everyone with the Team role:

  1. In the same Discord application as the bot, open OAuth2, copy the Client ID and Client Secret, and add a redirect: https://YOUR-SERVER/admin/auth/callback.
  2. Set DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET and PUBLIC_URL (your server's address). With DISCORD_GUILD_ID set, anyone with the Team role can sign in. To let in someone without the role, add their Discord user id to TEAM_DISCORD_IDS.

For local work, TEAM_DEV_LOGIN=true adds a one-click "local teammate" sign-in. Never set it on a public server.

Email

Support is async, so nobody should have to keep a tab open waiting. With email set up, a team reply the user hasn't seen in the widget for 5 minutes is emailed to them (at most one email per conversation an hour, with every unread reply in it). The email links to a page with just that conversation, and replying to the email posts into the conversation, so it reaches the dashboard and Discord too. Each email has an unsubscribe link for that conversation. The address comes from the signed-in user's token, user.email in Wicket.init, or the email field in the widget (askEmail in a profile), and the "passed to the team" message adds "We'll email you when they do."

It uses Resend; the free tier is plenty for a small team.

  1. In Resend, verify the domain you'll send from and create an API key. Set RESEND_API_KEY, EMAIL_FROM (e.g. Escape Director <[email protected]>) and PUBLIC_URL.
  2. For replies by email: add a receiving domain in Resend (e.g. reply.escapedirector.com) and set it as EMAIL_REPLY_DOMAIN. Add a webhook for email.received pointing at https://YOUR-SERVER/email/inbound and set its signing secret as RESEND_WEBHOOK_SECRET. A reply only counts if it comes from the address we emailed, to that conversation's own reply address.

MCP server

Wicket is also an MCP server, so people can reach your docs and your tracker from Claude Code, Claude Desktop, Cursor or any other MCP client. It uses the same code as the widget: the same docs index, the same tracker, the same duplicate check, and the same per-profile rules.

ToolWhat it does
search_docsSearches the docs index. Returns sections with a snippet and link.
read_docThe full text behind a search result.
file_reportFiles a bug or feature request. The first call checks open tickets, and if some might be the same thing, it files nothing and sends them back. The client then calls again with duplicate_of (joins that ticket as another report) or new_ticket: true. duplicate_of only takes a ticket it offered for that report, or one the person already reported. If the check can't reach the tracker, nothing is filed. Only shows up when the person's profile has report: bug, feature or both, and only offers those kinds.
ticket_statusWhere the person's reports stand (open, planned, fixed, declined), with the ticket and any team replies.

Each report becomes a conversation like one from the widget. The team sees it in Discord and the dashboard, and the reporter hears back in it (ticket_status reads it, and the chat_url it returns opens it in a browser). The client's own model does the writing, so MCP calls don't spend the assistant's budgets.

Over HTTP

Set MCP=true (with IDENTITY_SECRET) and the server answers at /mcp (streamable HTTP, stateless). It's off by default, since it lets people file into your tracker. Every request needs a user token, the same JWT the widget uses (see Signed-in users), as Authorization: Bearer <token>. Tokens can last up to 24 hours, so give people a way to get one in your app, like a "Connect Claude" button that signs a token for them.

Limits: each tool call counts against MAX_MESSAGES_PER_CONVERSATION_PER_HOUR for that person. Each report counts as starting a chat (MAX_CONVERSATIONS_PER_IP_PER_HOUR), both for that person and for their IP. In the demo, calls also share the widget's per-IP limits.

Claude Code:

bash
claude mcp add --transport http wicket https://YOUR-SERVER/mcp --header "Authorization: Bearer $WICKET_TOKEN"

Cursor (~/.cursor/mcp.json):

json
{ "mcpServers": { "wicket": { "url": "https://YOUR-SERVER/mcp", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } }

Claude Desktop's config file takes local servers, so bridge it with mcp-remote (Settings › Developer › Edit Config):

json
{  "mcpServers": {    "wicket": {      "command": "npx",      "args": ["-y", "mcp-remote", "https://YOUR-SERVER/mcp", "--header", "Authorization:${WICKET_AUTH}"],      "env": { "WICKET_AUTH": "Bearer YOUR_TOKEN" }    }  }}

Try it on the demo

wicketchat.com/demo has a Try it from Claude Code card. It runs the bot check, then gives you a claude mcp add command with a day-long token for Sam, the demo customer. Then ask Claude Code something like "search the Escape Director docs for adding a clue", "report a bug: the clue counter doesn't go up after I send a clue", or "check on my reports". Reports go to the demo tracker and move on after a few minutes, like in the widget, and they show up in the widget's history in the same browser.

Over stdio

For a client on the machine with Wicket's config and database, such as a self-hosted install:

bash
npm run buildclaude mcp add wicket -- sh -c 'cd /path/to/wicket && node --env-file-if-exists=.env dist/src/mcp/stdio.js'

Claude Desktop:

json
{  "mcpServers": {    "wicket": {      "command": "node",      "args": ["--env-file=/path/to/wicket/.env", "/path/to/wicket/dist/src/mcp/stdio.js"],      "env": { "DATABASE_PATH": "/path/to/wicket/data/wicket.db", "WICKET_CONFIG": "/path/to/wicket/config/wicket.yaml" }    }  }}

It uses the same settings, database and tracker as the server, and acts as the local user, or as whoever WICKET_USER_TOKEN names. It doesn't start the Discord bot, so reports filed this way go to the tracker and the dashboard but don't open a Discord thread. Run it alongside the server, which does the ticket checks that tell reporters when things move.

Deploy on Railway

  1. In Railway, choose New → GitHub Repo and pick this repo. It builds from the Dockerfile and redeploys on every push to main. The build runs the typecheck and tests first, so a failing push never replaces the running version.
  2. Add a Volume mounted at /data. SQLite lives there.
  3. Copy .env.example into the service variables and fill them in.
  4. Generate a domain and use it in the script tag above.

Keep it to one replica. SQLite and the Discord connection expect a single instance.

Hosted demo

The public demo at wicketchat.com is a separate Wicket service with DEMO=true. It runs the real server and real assistant, but nothing it does can reach a real team, tracker or inbox:

  • / is the marketing site with the live widget, as a visitor. Nobody is signed in anywhere except /demo with Signed-in customer picked.
  • /demo plays an escape-room app, with a Visitor / Signed-in customer switch. Visitors get docs answers only. The customer is Sam, Owner at Treehouse Escapes, signed in by the page's own POST /api/demo/token. Each browser gets its own id in a cookie, so nobody sees anyone else's chats. The endpoint only exists with DEMO=true and only signs that one user.
  • Tickets go to a demo tracker kept in the service's SQLite. Duplicate search only covers the backlog in config/demo.yaml, so one visitor's ticket titles never show up in another visitor's chat. A few minutes after a report (demo.advanceMinutes), the ticket moves on (bugs to Fixed, ideas to Planned) and a reply labeled as simulated arrives in the chat.
  • MCP: /demo hands out a token for Claude Code after the bot check (POST /api/demo/mcp-token), for the same Sam as that browser's chats. It lasts a day, and only these tokens open the demo's /mcp: the widget's own token comes without a bot check, so it doesn't. A report filed over MCP doesn't get assistant answers in its chat, since no bot check opened it. /mcp files to the same demo tracker, so it can't reach a real one either. See Try it on the demo.
  • Handoffs don't go anywhere. The chat says that in a real install this goes to the team's Discord inbox.
  • Limits: everyone shares one monthly AI budget (budgets.monthlyTotal, $3). When it's spent, the chat says so and still links plain docs search results. Every chat passes a Turnstile bot check, signed in or not. Messages are capped in length and per chat, and both messages and chats are capped per IP per hour (demo: in config/demo.yaml). Attachments and the playground are off.

Service variables:

VariableValue
DEMOtrue
WICKET_CONFIGconfig/demo.yaml
ANTHROPIC_API_KEY or AI_GATEWAY_API_KEYthe AI key. Without one, answers come from the scripted model and /demo says so.
IDENTITY_SECRETa fresh random secret of 32+ characters, not the one any real app uses
TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEYa Turnstile widget for the demo's domain
SITEleave unset (on) to serve the marketing site at /
PRODUCT_NAMEEscape Director
MCPtrue, for the Claude Code card on /demo and /mcp

Add a volume at /data as usual. The docs come from knowledge: in config/demo.yaml, so no DOCS_* variables are needed.

With DEMO=true, Wicket refuses to start if any LINEAR_*, GITHUB_ISSUES_*, DISCORD_*, RESEND_*, EMAIL_* or TEAM_DISCORD_IDS variable is set, if RELAY isn't none, if TEAM_DEV_LOGIN=true, or if IDENTITY_SECRET or the Turnstile keys are missing. It also refuses settings with appTools, announcements.feed or assistant.helpForum. So it can't be pointed at Escape Director's real Linear, GitHub, Discord or API by mistake.

To try it locally, use Cloudflare's always-pass test keys:

bash
npm run build:widgetDEMO=true WICKET_CONFIG=config/demo.yaml IDENTITY_SECRET=local-demo-secret-local-demo-secret-1234 \  TURNSTILE_SITE_KEY=1x00000000000000000000AA TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA \  DATABASE_PATH=./data/demo.db npx tsx src/index.ts   # open http://localhost:4318/demo

Profiles, rules and budgets

How the assistant behaves lives in one YAML file: config/wicket.yaml, or the YAML pasted into the WICKET_CONFIG variable. config/wicket.example.yaml lists every option.

yaml
profiles:  visitor:                # not signed in    ai: false             # no assistant: messages go straight to the team    askEmail: true        # offer an email field so the team can follow up  default:    budget: 0.50          # dollars per person per month    report: both          # "Report a bug" and "Request a feature" shortcuts  analytics:    instructions: Explain metrics using the docs.    suggestions: ["What does Success Rate mean?"]    budget: 2.00rules:                    # first match wins; otherwise "default"  - when: { signedIn: false }    profile: visitor  - when: { page: /app/analytics, plan: pro }    profile: analyticsbudgets:  warnAt: 0.8             # warn at 80% of a budget  monthlyTotal: 20        # cap across everyone
  • Theme (top level) sets the widget's look for everyone, with the same fields as Wicket.init({ theme }), so the embed snippet can stay bare.
  • Assistant (top level) applies everywhere, including the community help forum: instructions for facts every answer should know, code (decline by default: no code, it links the closest guide or example; snippets allows short snippets adapted from examples it has read), and helpForum (false by default; true answers new #help posts, which no per-person budget covers).
  • Profiles set whether the assistant answers at all, extra instructions, whether it stays on topic (offTopic: decline by default, so it won't do general coding or homework; topics widens what counts), the model and effort, a monthly budget per person, and what the widget shows (title, welcome text, placeholder, quick starts). Options passed to Wicket.init() still win.
  • Rules pick a profile from whether someone is signed in, their plan, and the page they're on. Signed in and plan come only from a verified token; the page comes from the widget.
  • Visitors can get the assistant too, without opening a hole in your budget: give their profile budgetScope: shared so they all spend from one pot, and botCheck: true so each conversation starts with a Cloudflare Turnstile check (set TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY; without them those visitors go straight to the team). tickets: false stops the assistant logging reports for people it can't follow up with.
  • Report shortcuts: report: bug, feature or both puts "Report a bug" and "Request a feature" on the widget's start screen, under an "Ask a question" card. Choosing one tags the conversation straight away, before the first message, and the tag stays until something is logged. For a bug, the assistant's first reply offers a known fix from the docs, if there is one, and proposes the steps from the widget's recent activity for the person to confirm ("Looks like you paused the game, then sent a Clue from the Room Dashboard. Is that right, and does it happen every time?"), at most two short questions. Unless they say the fix solved it, it logs the bug in its next reply with what it has: the confirmed steps under ## Steps to reproduce, the widget's context, and the trail under ## Recent activity. A feature request is restated and logged. Either way a repeat goes on the ticket that already exists, and the assistant never decides whether a report is worth filing. The shortcuts are an extra way in: the assistant still logs bugs, ideas and docs gaps it hears about in any conversation on its own, just as without them. It's none by default. The server checks the profile, so a browser can't start a report where the profile doesn't offer one, and budgets and rate limits apply as usual. Shortcuts need a tracker and the assistant; on a profile with tickets: false they're off, and the boot log says so.
  • Follow-ups: followUps: true has the assistant suggest two or three questions to ask next with each answer, shown as chips. They're written with the answer, so they cost no extra call.
  • Budgets count what each person's assistant answers cost this calendar month (UTC), from what the AI Gateway billed for each response, or from token usage and list prices when it doesn't say. Signed-in people are counted by user id, visitors by IP. Each person has one total across all profiles; a profile's budget is the point where that profile stops answering, so giving every profile the same number makes it a single shared budget. Near the limit the widget says so; at the limit their messages go to the team instead. monthlyTotal does the same for everyone at once. Prices for current Claude models and GPT-6 Luna are built in; add others under budgets.prices.

On boot the log lists each profile and anything that won't work as written, such as rules about sign-in without IDENTITY_SECRET.

Configuration

See .env.example. The main settings:

VariableWhat it does
AI_GATEWAY_API_KEY or ANTHROPIC_API_KEYTurns on the assistant, through the Vercel AI Gateway or Anthropic directly. Leave both empty to send every chat to the team.
AGENT_MODEL, AGENT_EFFORTModel and effort for the assistant (defaults: claude-haiku-5-5, low).
DOCS_GITHUB_REPO, DOCS_BASE_URLMarkdown docs repo to index, and the public site its links should point to. Reindexed every DOCS_REINDEX_HOURS.
DOCS_URLNo docs repo? A hosted docs site to index instead: its home page (pages under that path are read) or its sitemap.xml. Pages come from the sitemap when there is one, otherwise by following links, up to 500.
RELAY, DISCORD_*Where conversations show up for the team.
ALLOWED_ORIGINSSites allowed to embed the widget.
PLAYGROUND, SITEPLAYGROUND serves a test page with the widget at / (on outside production). SITE serves the Wicket marketing page (site/index.html) at /, and moves the playground to /playground. It's on by default; SITE=false turns it off.
DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, PUBLIC_URLDiscord sign-in for the team dashboard at /admin. TEAM_DISCORD_IDS lets in people without the Team role.
RESEND_API_KEY, EMAIL_FROM, EMAIL_REPLY_DOMAIN, RESEND_WEBHOOK_SECRETEmailing unread replies to users, and replies by email. See Email.
IDENTITY_SECRETShared with your backend to sign user tokens (at least 32 characters).
TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEYCloudflare Turnstile, for profiles with botCheck: true.
MCPtrue turns on the MCP server at /mcp (needs IDENTITY_SECRET). Off by default. See MCP server.
WICKET_CONFIGProfiles, rules, budgets and extra knowledge repos (knowledge:, e.g. an SDK with examples): a file path or the YAML itself. Defaults to config/wicket.yaml.

How it fits together

widget.js ──▶ Hono API ──▶ ConversationService ──▶ Agent (Claude + tools: search_docs, handoff_to_human)                 ▲               │                         │         SSE live updates        ├──▶ SQLite (conversations, messages, transcript, FTS5 docs index)                                 └──▶ Relay adapter (Discord) ◀── team replies                 ▲/admin dashboard ┘ (team replies, notes, typing; live over SSE)

Each external tool sits behind an adapter interface in src/adapters/types.ts (KnowledgeSource, Relay), so Slack, other docs sources or trackers plug in without touching the core.

Development

bash
npm test            # unit + API tests (scripted model, fake relay)npm run typecheckcp .env.example .env   # fill in your keysnpm run dev            # open http://localhost:4318 for a test page with the widget

Comparing models

evals/cases.yaml is a set of real-world questions, each with the kind of reply it should get (an answer that links the right page, a handoff, a polite decline, and so on). npm run eval sends each one to the model with your .env settings and docs, scores the replies without an AI judge, and prints pass rate, cost, time and tool calls per model:

bash
npm run eval -- --models claude-haiku-5-5,openai/gpt-6-luna   # 2 runs per case by defaultnpm run eval -- --only 24,25 --reps 1npm run eval -- --only 28,29,30,31,32,33,34,35,36,37,38,39   # just the routing cases

Each case runs as a chat with a signed-in person, through the same conversation code as the server, with a fake tracker that starts from the file's backlog of open tickets. Routing cases (kind: route) check what landed in it: a repeat report added to the right existing ticket, a new bug, idea or docs gap filed once with the right kind, or nothing logged for a how-to question. Cases with report: bug or report: feature start from the widget's shortcut, so the assistant should gather the details and log them rather than decide whether to; for a bug it offers a fix from the docs first, and logs nothing only when the person says the fix worked. A case's trail is sent as the widget's recent activity; kind: steps checks that the assistant proposes the steps from it for the person to confirm instead of asking what they did. Every case runs with the shortcuts on, so the routing cases also check that the assistant keeps logging on its own. No real ticket is ever created.

Full replies are saved to evals/results/ (not committed). A run of every case twice on two models costs well under a dollar.

來源:README.md,提交 e522919

工具

0
該伺服器需要 API 金鑰等憑證,因此 SourceWeft 無法在沒有憑證的情況下讀取其工具,也未收錄。

版本歷史

1
  1. v1.0.0最新Nov 14, 2026

評分

已滿足適用檢查項的 85%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分