@capitalthought/agent-feedback
v0.1.0
Published
Operator feedback + error reporting for agent-first MCP products. Drop-in tool factory + error-wrap middleware.
Maintainers
Readme
@capitalthought/agent-feedback
Operator feedback + error reporting for agent-first MCP products. Drop-in tool factory + error-wrap middleware for capturing feedback from inside Claude/Mikey conversations.
Why
In agent-first products, operators interact through an LLM (Mikey, Claude) — not a UI. When an operator says "this is weird" or hits a tool error, that signal evaporates the moment the conversation ends. This package gives you:
- An explicit MCP tool (
<product>_feedback) the agent calls when the operator signals friction. - A middleware (
wrapWithFeedback) that auto-captures user-facing tool errors with a structuredfeedback_idreturned to the LLM.
Both paths use the operator's existing JWT — no new shared secrets, no impersonation vector.
Install
npm install @capitalthought/agent-feedbackUsage
1. Implement FeedbackStorage against your backend
import type { FeedbackStorage, FeedbackInput, FeedbackRecord } from '@capitalthought/agent-feedback';
export class MyFeedbackStorage implements FeedbackStorage {
async create(input: FeedbackInput): Promise<FeedbackRecord> { /* INSERT into your DB */ }
async getById(id: string): Promise<FeedbackRecord | null> { /* SELECT */ }
}2. Register the explicit tool in your MCP server
import { createFeedbackTool } from '@capitalthought/agent-feedback';
const feedbackTool = createFeedbackTool({
apiBaseUrl: 'https://yourproduct.com',
productName: 'yourproduct',
getAuthToken: async () => operatorJwt, // your existing operator JWT cache
});
server.registerTool(feedbackTool);3. Wrap your existing tool handlers with the middleware
import { wrapWithFeedback, defaultClassifier } from '@capitalthought/agent-feedback';
const userFacingErrors = new Set(['mfa_required', 'address_required', /* ... */]);
const infraNoise = new Set(['rate_limited', 'transient_5xx']);
const sessionId = `${operatorEmail}-${Date.now()}`;
const wrapped = wrapWithFeedback('yourproduct_launch', originalHandler, {
apiBaseUrl: 'https://yourproduct.com',
productName: 'yourproduct',
getAuthToken: async () => operatorJwt,
classifier: (code, tool) => defaultClassifier(code, tool, {
userFacingErrorCodes: userFacingErrors,
infraNoiseErrorCodes: infraNoise,
}),
sessionId,
skipToolNames: new Set(['yourproduct_feedback']), // never wrap the feedback tool itself
});4. Implement POST /api/feedback server-side
The middleware POSTs to ${apiBaseUrl}/api/feedback with the operator JWT in the Authorization header. Your server should:
- Validate the JWT and derive
operator_emailfrom it (NOT from the request body) - Validate
summary+contextfor PII (no emails, phones, SSNs, URLs with querystrings, HTML) - Insert via your
FeedbackStorage - Return
{ short_id: 'fb_XXXXXXXXXX' }
See AGENTS.md for the full agent contract.
Default classifier
defaultClassifier(errorCode, toolName, options) is default-suppress on unknown error codes. Only codes in userFacingErrorCodes are captured. This avoids polluting the triage queue with novel infra noise.
Per-session rate limit
The middleware caps feedback creates at 3 per session (configurable via cap option). Beyond the cap, captures are silently dropped — the original tool response is returned untouched.
Recursion guard
Pass the explicit feedback tool's name in skipToolNames to prevent the middleware from trying to record feedback about a failed feedback create.
License
UNLICENSED — internal Capital Thought tooling. Open-sourced for transparency only.
