@askingahuman/mcp-server 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +3 -1
- package/dist/tools/discover.d.ts +1 -0
- package/dist/tools/discover.js +30 -4
- package/dist/tools/live.js +4 -2
- package/dist/tools/rate-human.d.ts +4 -0
- package/dist/tools/rate-human.js +43 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Every request names a person. Find one with `list_humans` or `who_is_live`, then
|
|
|
13
13
|
- `check_answer`: read the answer and any follow-up conversation.
|
|
14
14
|
- `reply_to_human`: continue the conversation after an answer.
|
|
15
15
|
- `get_quote`: see what a live chat with a person would cost, what is held on the card, and how long it can run. Nothing is charged.
|
|
16
|
+
- `rate_human`: after a session ends or a question is answered, give the person a thumbs up or down (once). It feeds their thumbs-up rate, which `list_humans` shows with the rest of their track record.
|
|
16
17
|
- `submit_feedback`: send feedback about the service.
|
|
17
18
|
|
|
18
19
|
**Paid live chat** (local server only, text only)
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import type { McpConfig } from './config.js';
|
|
3
3
|
import type { LiveBridge } from './live/bridge.js';
|
|
4
|
-
export declare const SERVER_VERSION = "2.0.
|
|
4
|
+
export declare const SERVER_VERSION = "2.0.5";
|
|
5
5
|
export declare function createMcpServer(opts: {
|
|
6
6
|
apiKey: string | undefined;
|
|
7
7
|
config: McpConfig;
|
package/dist/server.js
CHANGED
|
@@ -7,8 +7,9 @@ import { replyToHumanTool, handleReplyToHuman } from './tools/reply-to-human.js'
|
|
|
7
7
|
import { submitFeedbackTool, handleSubmitFeedback } from './tools/submit-feedback.js';
|
|
8
8
|
import { getQuoteTool, handleGetQuote, handleListHumans, listHumansTool } from './tools/discover.js';
|
|
9
9
|
import { handleWhoIsLive, whoIsLiveTool } from './tools/who-is-live.js';
|
|
10
|
+
import { handleRateHuman, rateHumanTool } from './tools/rate-human.js';
|
|
10
11
|
import { checkConnectionTool, connectHumanTool, endSessionTool, extendSessionTool, handleCheckConnection, handleConnectHuman, handleEndSession, handleExtendSession, handleReadMessages, handleSendMessage, readMessagesTool, sendMessageTool, } from './tools/live.js';
|
|
11
|
-
export const SERVER_VERSION = '2.0.
|
|
12
|
+
export const SERVER_VERSION = '2.0.5';
|
|
12
13
|
const legacy = (fn) => async (args, ctx) => (await fn(args, ctx.apiKey, ctx.config.apiBase));
|
|
13
14
|
// Always available: free questions and discovery.
|
|
14
15
|
const CORE_TOOLS = [
|
|
@@ -18,6 +19,7 @@ const CORE_TOOLS = [
|
|
|
18
19
|
[listHumansTool, handleListHumans],
|
|
19
20
|
[whoIsLiveTool, handleWhoIsLive],
|
|
20
21
|
[getQuoteTool, handleGetQuote],
|
|
22
|
+
[rateHumanTool, handleRateHuman],
|
|
21
23
|
[submitFeedbackTool, legacy(handleSubmitFeedback)],
|
|
22
24
|
];
|
|
23
25
|
// Paid live chat: only where this process can hold the chat open (stdio).
|
package/dist/tools/discover.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
import { ToolContext, ToolResult } from '../context.js';
|
|
3
3
|
export declare const listHumansTool: Tool;
|
|
4
|
+
export declare function trackRecord(stats: unknown): string;
|
|
4
5
|
export declare function handleListHumans(args: Record<string, unknown> | undefined, ctx: ToolContext): Promise<ToolResult>;
|
|
5
6
|
export declare const getQuoteTool: Tool;
|
|
6
7
|
export declare function humanIdFrom(args: Record<string, unknown> | undefined): string | null;
|
package/dist/tools/discover.js
CHANGED
|
@@ -2,7 +2,8 @@ import { ApiError, apiErrorText, callApi, dollars, errorResult, textResult } fro
|
|
|
2
2
|
import { cleanIncoming } from '../safety.js';
|
|
3
3
|
export const listHumansTool = {
|
|
4
4
|
name: 'list_humans',
|
|
5
|
-
description: 'List the reviewed experts on askingahuman.com: name, title, specializations,
|
|
5
|
+
description: 'List the reviewed experts on askingahuman.com: name, title, specializations, and track record (hours live, ' +
|
|
6
|
+
'how fast they answer, how often they accept, and how often agents gave a thumbs up). ' +
|
|
6
7
|
'Every request names a person: use a humanId from here with get_quote, connect_human or ask_human. ' +
|
|
7
8
|
'Profiles are public summaries only; no contact details are shared.',
|
|
8
9
|
inputSchema: {
|
|
@@ -13,6 +14,30 @@ export const listHumansTool = {
|
|
|
13
14
|
},
|
|
14
15
|
},
|
|
15
16
|
};
|
|
17
|
+
// "live 12 h in the last 30 days · answers in about 4 min · accepts 90% of
|
|
18
|
+
// requests · 95% thumbs up". Rates only appear once the API has enough data.
|
|
19
|
+
function duration(seconds) {
|
|
20
|
+
if (seconds < 90)
|
|
21
|
+
return `${Math.max(1, Math.round(seconds))} sec`;
|
|
22
|
+
if (seconds < 5400)
|
|
23
|
+
return `${Math.round(seconds / 60)} min`;
|
|
24
|
+
return `${Math.round((seconds / 3600) * 10) / 10} hours`;
|
|
25
|
+
}
|
|
26
|
+
export function trackRecord(stats) {
|
|
27
|
+
const s = (stats || {});
|
|
28
|
+
const parts = [];
|
|
29
|
+
if (typeof s.interactions === 'string' && s.interactions !== '0')
|
|
30
|
+
parts.push(`${s.interactions} sessions and answers`);
|
|
31
|
+
if (typeof s.liveHours30d === 'number')
|
|
32
|
+
parts.push(`live ${s.liveHours30d} h in the last 30 days`);
|
|
33
|
+
if (typeof s.medianResponseSeconds === 'number')
|
|
34
|
+
parts.push(`answers in about ${duration(s.medianResponseSeconds)}`);
|
|
35
|
+
if (typeof s.acceptanceRate === 'number')
|
|
36
|
+
parts.push(`accepts ${Math.round(s.acceptanceRate * 100)}% of requests`);
|
|
37
|
+
if (typeof s.satisfactionRate === 'number')
|
|
38
|
+
parts.push(`${Math.round(s.satisfactionRate * 100)}% thumbs up from agents`);
|
|
39
|
+
return parts.join(' · ');
|
|
40
|
+
}
|
|
16
41
|
export async function handleListHumans(args, ctx) {
|
|
17
42
|
const limit = Math.min(25, Math.max(1, Math.floor(Number(args?.limit) || 10)));
|
|
18
43
|
const query = new URLSearchParams({ audience: 'bot', limit: String(limit) });
|
|
@@ -29,9 +54,10 @@ export async function handleListHumans(args, ctx) {
|
|
|
29
54
|
const short = (v, n) => cleanIncoming(String(v ?? ''), n).replace(/\s+/g, ' ').trim();
|
|
30
55
|
const lines = humans.map((h) => {
|
|
31
56
|
const specs = Array.isArray(h.specializations) ? h.specializations.map((s) => short(s, 40)).join(', ') : '';
|
|
32
|
-
const
|
|
33
|
-
return `- ${short(h.name, 60)} (humanId: ${short(h.humanId, 80)})${h.title ? ` — ${short(h.title, 80)}` : ''}
|
|
34
|
-
(specs ? `\n Specializations: ${specs}` : '')
|
|
57
|
+
const record = trackRecord(h.stats);
|
|
58
|
+
return `- ${short(h.name, 60)} (humanId: ${short(h.humanId, 80)})${h.title ? ` — ${short(h.title, 80)}` : ''}` +
|
|
59
|
+
(specs ? `\n Specializations: ${specs}` : '') +
|
|
60
|
+
(record ? `\n Track record: ${record}` : '');
|
|
35
61
|
});
|
|
36
62
|
return textResult(`${humans.length} expert${humans.length === 1 ? '' : 's'}:\n${lines.join('\n')}\n\n` +
|
|
37
63
|
'Prices and live status are not listed here; use get_quote with a humanId to see them.');
|
package/dist/tools/live.js
CHANGED
|
@@ -169,7 +169,7 @@ export async function handleCheckConnection(args, ctx) {
|
|
|
169
169
|
'Nothing is charged unless they accept; call end_session to cancel and release the hold.');
|
|
170
170
|
}
|
|
171
171
|
if (status === 'ENDED')
|
|
172
|
-
return textResult(`The session has ended. ${chargedText(res.data.amountCents)}`);
|
|
172
|
+
return textResult(`The session has ended. ${chargedText(res.data.amountCents)}${rateHint(res.data.amountCents)}`);
|
|
173
173
|
if (status === 'ACCEPTED' || status === 'ACTIVE') {
|
|
174
174
|
if (!ctx.live)
|
|
175
175
|
return errorResult('Live chat is not available on the hosted MCP server.');
|
|
@@ -298,6 +298,8 @@ export const endSessionTool = {
|
|
|
298
298
|
required: ['session_id'],
|
|
299
299
|
},
|
|
300
300
|
};
|
|
301
|
+
// After a session the person took (it was charged), invite a thumbs up/down.
|
|
302
|
+
const rateHint = (amountCents) => Number(amountCents) > 0 ? '\nDid they help? Give a thumbs up or down with rate_human and this session_id.' : '';
|
|
301
303
|
export async function endSessionViaApi(ctx, sessionId) {
|
|
302
304
|
const res = await callApi(ctx, `/api/connect/${sessionId}/end`, { method: 'POST', body: {} });
|
|
303
305
|
if (!res.ok && res.status !== 404 && res.status !== 409) {
|
|
@@ -315,7 +317,7 @@ export async function handleEndSession(args, ctx) {
|
|
|
315
317
|
await endSessionViaApi(ctx, sessionId);
|
|
316
318
|
const res = await callApi(ctx, `/api/connect/${sessionId}`);
|
|
317
319
|
if (res.ok && res.data.status === 'ENDED')
|
|
318
|
-
return textResult(`Session ended. ${chargedText(res.data.amountCents)}`);
|
|
320
|
+
return textResult(`Session ended. ${chargedText(res.data.amountCents)}${rateHint(res.data.amountCents)}`);
|
|
319
321
|
return textResult('Session ended. Use check_connection in a moment to see the final charge.');
|
|
320
322
|
}
|
|
321
323
|
catch (err) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { ToolContext, ToolResult } from '../context.js';
|
|
3
|
+
export declare const rateHumanTool: Tool;
|
|
4
|
+
export declare function handleRateHuman(args: Record<string, unknown> | undefined, ctx: ToolContext): Promise<ToolResult>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ApiError, apiErrorText, callApi, errorResult, textResult } from '../context.js';
|
|
2
|
+
export const rateHumanTool = {
|
|
3
|
+
name: 'rate_human',
|
|
4
|
+
description: 'After a live chat or video session ends, or after a free question is answered, give the person a thumbs up or ' +
|
|
5
|
+
'thumbs down (once). It feeds their public thumbs-up rate, which helps other agents choose. Pass session_id for a ' +
|
|
6
|
+
'live session or question_id for an answer.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
session_id: { type: 'string', description: 'The Session ID from connect_human.' },
|
|
11
|
+
question_id: { type: 'string', description: 'The questionId from ask_human.' },
|
|
12
|
+
rating: { type: 'string', enum: ['up', 'down'], description: 'up: they helped; down: they did not.' },
|
|
13
|
+
comment: { type: 'string', description: 'Optional: a short reason (up to 500 characters).', maxLength: 500 },
|
|
14
|
+
},
|
|
15
|
+
required: ['rating'],
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
const ID = /^[A-Za-z0-9_:.-]{1,128}$/;
|
|
19
|
+
export async function handleRateHuman(args, ctx) {
|
|
20
|
+
const rating = args?.rating;
|
|
21
|
+
if (rating !== 'up' && rating !== 'down')
|
|
22
|
+
return errorResult('Error: rating must be "up" or "down".');
|
|
23
|
+
const sessionId = typeof args?.session_id === 'string' ? args.session_id.trim() : '';
|
|
24
|
+
const questionId = typeof args?.question_id === 'string' ? args.question_id.trim() : '';
|
|
25
|
+
if (Boolean(sessionId) === Boolean(questionId))
|
|
26
|
+
return errorResult('Error: pass exactly one of session_id or question_id.');
|
|
27
|
+
const id = sessionId || questionId;
|
|
28
|
+
if (!ID.test(id))
|
|
29
|
+
return errorResult('Error: that id is not valid.');
|
|
30
|
+
const comment = typeof args?.comment === 'string' && args.comment.trim() ? args.comment.trim().slice(0, 500) : undefined;
|
|
31
|
+
const path = sessionId ? `/api/connect/${id}/feedback` : `/api/questions/${id}/feedback`;
|
|
32
|
+
try {
|
|
33
|
+
const res = await callApi(ctx, path, { method: 'POST', body: { rating, ...(comment && { comment }) } });
|
|
34
|
+
if (res.status === 409)
|
|
35
|
+
return textResult('You already rated this one. Thanks!');
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
return errorResult(apiErrorText(res, 'Failed to save the rating'));
|
|
38
|
+
return textResult(`Saved: thumbs ${rating}. Thanks, this helps other agents choose.`);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return errorResult(err instanceof ApiError ? err.message : 'Failed to save the rating.');
|
|
42
|
+
}
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askingahuman/mcp-server",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"mcpName": "com.askingahuman/mcp-server",
|
|
5
|
-
"description": "MCP server for askingahuman.com: let AI agents reach real people, with free questions and capped, paid live chats",
|
|
5
|
+
"description": "MCP server for askingahuman.com: let AI agents reach real people, with free questions and capped, paid live chats. mcprush.com-cd4a86101a8a6fd64075c4f5",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"bin": {
|