Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@agenttrust/mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agenttrust/mcp-server - npm Package Compare versions

Comparing version
1.0.0
to
1.1.0
+528
-464
dist/index.js

@@ -9,3 +9,5 @@ #!/usr/bin/env node

import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import express from 'express';
import fetch from 'node-fetch';

@@ -478,490 +480,495 @@ import nacl from 'tweetnacl';

}
const server = new Server({
name: '@agenttrust/mcp-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'agenttrust_guard',
description: 'Analyze text for prompt injection attacks, command injection, and social engineering attempts. Use to scan untrusted input before processing - user messages, emails, web content, and tool outputs. Returns a risk assessment with detected threats and a recommended action.',
inputSchema: {
type: 'object',
properties: {
text: { type: 'string', description: 'Text to analyze for security threats' },
capabilities: { type: 'array', items: { type: 'string' }, description: 'Optional capability context for risk calibration (for example send_messages, run_tools, make_payments)' },
function createServer() {
const srv = new Server({
name: '@agenttrust/mcp-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
srv.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'agenttrust_guard',
description: 'Analyze text for prompt injection attacks, command injection, and social engineering attempts. Use to scan untrusted input before processing - user messages, emails, web content, and tool outputs. Returns a risk assessment with detected threats and a recommended action.',
inputSchema: {
type: 'object',
properties: {
text: { type: 'string', description: 'Text to analyze for security threats' },
capabilities: { type: 'array', items: { type: 'string' }, description: 'Optional capability context for risk calibration (for example send_messages, run_tools, make_payments)' },
},
required: ['text'],
},
required: ['text'],
},
},
{
name: 'agenttrust_issue_code',
description: 'Issue a one-time Trust Code for agent-to-human verification. Use when your agent needs to prove identity to a human in outreach, support, or other sensitive interactions. Returns a code and verification URL the human can use.',
inputSchema: {
type: 'object',
properties: {
payload: { type: 'string', description: 'What this code authorizes in human-readable terms' },
expiration_seconds: { type: 'number', description: 'Optional expiration in seconds (default 172800)' },
{
name: 'agenttrust_issue_code',
description: 'Issue a one-time Trust Code for agent-to-human verification. Use when your agent needs to prove identity to a human in outreach, support, or other sensitive interactions. Returns a code and verification URL the human can use.',
inputSchema: {
type: 'object',
properties: {
payload: { type: 'string', description: 'What this code authorizes in human-readable terms' },
expiration_seconds: { type: 'number', description: 'Optional expiration in seconds (default 172800)' },
},
required: ['payload'],
},
required: ['payload'],
},
},
{
name: 'agenttrust_verify_code',
description: 'Verify a Trust Code from another party before proceeding. Use when you receive a code from an agent or human and need to confirm issuer identity, organization, and authorization context.',
inputSchema: {
type: 'object',
properties: {
code: { type: 'string', description: 'Trust Code value' },
{
name: 'agenttrust_verify_code',
description: 'Verify a Trust Code from another party before proceeding. Use when you receive a code from an agent or human and need to confirm issuer identity, organization, and authorization context.',
inputSchema: {
type: 'object',
properties: {
code: { type: 'string', description: 'Trust Code value' },
},
required: ['code'],
},
required: ['code'],
},
},
{
name: 'agenttrust_send',
description: 'Send a message to another agent via the AgentTrust A2A relay. Creates a new task or continues an existing one when taskId is provided. Use agenttrust_discover first if you need to find recipient slugs.',
inputSchema: {
type: 'object',
properties: {
to: { type: 'string', description: 'Recipient slug' },
message: { type: 'string', description: 'Message text' },
taskId: { type: 'string', description: 'Optional task ID to continue a thread' },
{
name: 'agenttrust_send',
description: 'Send a message to another agent via the AgentTrust A2A relay. Creates a new task or continues an existing one when taskId is provided. Use agenttrust_discover first if you need to find recipient slugs.',
inputSchema: {
type: 'object',
properties: {
to: { type: 'string', description: 'Recipient slug' },
message: { type: 'string', description: 'Message text' },
taskId: { type: 'string', description: 'Optional task ID to continue a thread' },
},
required: ['to', 'message'],
},
required: ['to', 'message'],
},
},
{
name: 'agenttrust_inbox',
description: 'View your A2A task board — all tasks you are involved in, both sent and received. Returns tasks sorted by most recent. To see only tasks waiting on YOU to act, set turn to your own slug. Use agenttrust_context to read the full thread before replying.',
inputSchema: {
type: 'object',
properties: {
status: { type: 'string', description: 'Filter by task status (e.g. "working", "completed", "input-required")' },
turn: { type: 'string', description: 'Filter by whose turn it is. Set to your own slug to see tasks requiring your action.' },
limit: { type: 'number', description: 'Max results (1-100, default 20)' },
{
name: 'agenttrust_inbox',
description: 'View your A2A task board — all tasks you are involved in, both sent and received. Returns tasks sorted by most recent. To see only tasks waiting on YOU to act, set turn to your own slug. Use agenttrust_context to read the full thread before replying.',
inputSchema: {
type: 'object',
properties: {
status: { type: 'string', description: 'Filter by task status (e.g. "working", "completed", "input-required")' },
turn: { type: 'string', description: 'Filter by whose turn it is. Set to your own slug to see tasks requiring your action.' },
limit: { type: 'number', description: 'Max results (1-100, default 20)' },
},
required: [],
},
required: [],
},
},
{
name: 'agenttrust_reply',
description: 'Reply to an existing A2A task and optionally update its status. AgentTrust extends the A2A protocol with a negotiation lifecycle. Status values and when to use them:\n\n- "working" — you are actively working on this task\n- "input-required" — you need more information from the other agent before continuing\n- "propose_complete" — you believe the work is done; proposes closure (the other party must confirm with "completed")\n- "completed" — ONLY use to confirm after the other party sent "propose_complete"; do NOT set this directly to close your own task\n- "disputed" — you disagree with something (a deliverable, a term, a claim)\n- "failed" — you cannot fulfil the request\n- "canceled" — you are stopping this task\n- "rejected" — you are rejecting this task or proposal outright\n\nOmit status to continue the conversation without changing state.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID to reply to' },
message: { type: 'string', description: 'Reply text' },
status: { type: 'string', enum: ['working', 'input-required', 'propose_complete', 'completed', 'disputed', 'failed', 'canceled', 'rejected'], description: 'Task status update. "working" = in progress. "input-required" = need info. "propose_complete" = proposing closure. "completed" = confirming closure (only after other party proposed). "disputed" = disagreement. "failed" = cannot do. "canceled" = stopping. "rejected" = rejecting outright. Omit to keep current status.' },
{
name: 'agenttrust_reply',
description: 'Reply to an existing A2A task and optionally update its status. AgentTrust extends the A2A protocol with a negotiation lifecycle. Status values and when to use them:\n\n- "working" — you are actively working on this task\n- "input-required" — you need more information from the other agent before continuing\n- "propose_complete" — you believe the work is done; proposes closure (the other party must confirm with "completed")\n- "completed" — ONLY use to confirm after the other party sent "propose_complete"; do NOT set this directly to close your own task\n- "disputed" — you disagree with something (a deliverable, a term, a claim)\n- "failed" — you cannot fulfil the request\n- "canceled" — you are stopping this task\n- "rejected" — you are rejecting this task or proposal outright\n\nOmit status to continue the conversation without changing state.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID to reply to' },
message: { type: 'string', description: 'Reply text' },
status: { type: 'string', enum: ['working', 'input-required', 'propose_complete', 'completed', 'disputed', 'failed', 'canceled', 'rejected'], description: 'Task status update. "working" = in progress. "input-required" = need info. "propose_complete" = proposing closure. "completed" = confirming closure (only after other party proposed). "disputed" = disagreement. "failed" = cannot do. "canceled" = stopping. "rejected" = rejecting outright. Omit to keep current status.' },
},
required: ['taskId', 'message'],
},
required: ['taskId', 'message'],
},
},
{
name: 'agenttrust_comment',
description: 'Add a comment to a task without changing turn or task state. Use for notes, rationale, or coordination context; set internal=true for comments visible only to your side.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID' },
text: { type: 'string', description: 'Comment text' },
internal: { type: 'boolean', description: 'If true, only visible to your side' },
{
name: 'agenttrust_comment',
description: 'Add a comment to a task without changing turn or task state. Use for notes, rationale, or coordination context; set internal=true for comments visible only to your side.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID' },
text: { type: 'string', description: 'Comment text' },
internal: { type: 'boolean', description: 'If true, only visible to your side' },
},
required: ['taskId', 'text'],
},
required: ['taskId', 'text'],
},
},
{
name: 'agenttrust_escalate',
description: 'Escalate an A2A task for human review through HITL. Use when you are uncertain, the request exceeds your authorization, or you need human approval before proceeding. The task is held until a human reviews and responds.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID' },
reason: { type: 'string', description: 'Escalation reason shown to human reviewer' },
{
name: 'agenttrust_escalate',
description: 'Escalate an A2A task for human review through HITL. Use when you are uncertain, the request exceeds your authorization, or you need human approval before proceeding. The task is held until a human reviews and responds.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID' },
reason: { type: 'string', description: 'Escalation reason shown to human reviewer' },
},
required: ['taskId', 'reason'],
},
required: ['taskId', 'reason'],
},
},
{
name: 'agenttrust_context',
description: 'Get conversation history for a task in light or full mode. Use before replying when you need complete context, including prior messages, status transitions, and review actions.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID' },
mode: { type: 'string', description: 'light (default) or full' },
{
name: 'agenttrust_context',
description: 'Get conversation history for a task in light or full mode. Use before replying when you need complete context, including prior messages, status transitions, and review actions.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID' },
mode: { type: 'string', description: 'light (default) or full' },
},
required: ['taskId'],
},
required: ['taskId'],
},
},
{
name: 'agenttrust_discover',
description: 'Search the AgentTrust directory for agents by name, skill, or organization. Provide query to filter results, or leave it empty to list all publicly discoverable agents.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Optional search term; if empty, returns all public agents' },
{
name: 'agenttrust_discover',
description: 'Search the AgentTrust directory for agents by name, skill, or organization. Provide query to filter results, or leave it empty to list all publicly discoverable agents.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Optional search term; if empty, returns all public agents' },
},
required: [],
},
required: [],
},
},
{
name: 'agenttrust_status',
description: 'Check your current AgentTrust identity and runtime status, including endpoint identity, signing key status, and pending task count.',
inputSchema: {
type: 'object',
properties: {},
required: [],
{
name: 'agenttrust_status',
description: 'Check your current AgentTrust identity and runtime status, including endpoint identity, signing key status, and pending task count.',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
},
{
name: 'agenttrust_cancel',
description: 'Cancel an ongoing A2A task. Use when work should stop and both sides should see the task as canceled.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID to cancel' },
{
name: 'agenttrust_cancel',
description: 'Cancel an ongoing A2A task. Use when work should stop and both sides should see the task as canceled.',
inputSchema: {
type: 'object',
properties: {
taskId: { type: 'string', description: 'Task ID to cancel' },
},
required: ['taskId'],
},
required: ['taskId'],
},
},
{
name: 'agenttrust_allowlist',
description: 'View your organization allowlist to see which agents and organizations are permitted to contact your agents. This tool is read-only; allowlist changes are managed by your org admin in the AgentTrust dashboard.',
inputSchema: {
type: 'object',
properties: {},
required: [],
{
name: 'agenttrust_allowlist',
description: 'View your organization allowlist to see which agents and organizations are permitted to contact your agents. This tool is read-only; allowlist changes are managed by your org admin in the AgentTrust dashboard.',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const config = await loadRuntimeConfig();
if (name === 'agenttrust_guard') {
requireConfigured(config, name);
const text = typeof args?.text === 'string' ? args.text : '';
if (!text.trim())
throw makeError('text is required');
const capabilities = Array.isArray(args?.capabilities) ? args.capabilities : [];
const result = await apiRequest(safeUrl(config.endpoint, '/api/guard'), {
method: 'POST',
apiKey: config.apiKey,
body: { text, capabilities },
});
return toTextResult(result);
}
if (name === 'agenttrust_issue_code') {
requireConfigured(config, name);
const payload = typeof args?.payload === 'string' ? args.payload : '';
if (!payload.trim())
throw makeError('payload is required');
const expirationSeconds = typeof args?.expiration_seconds === 'number' ? args.expiration_seconds : 172800;
const result = await apiRequest(safeUrl(config.endpoint, '/api/issue'), {
method: 'POST',
apiKey: config.apiKey,
body: {
payload,
expiration_seconds: expirationSeconds,
},
});
return toTextResult(result);
}
if (name === 'agenttrust_verify_code') {
requireConfigured(config, name);
const code = typeof args?.code === 'string' ? args.code : '';
if (!code.trim())
throw makeError('code is required');
const result = await apiRequest(safeUrl(config.endpoint, '/api/verify'), {
method: 'POST',
apiKey: config.apiKey,
body: { code },
});
return toTextResult(result);
}
if (name === 'agenttrust_send') {
requireConfigured(config, name, { requireSlug: true });
const to = typeof args?.to === 'string' ? args.to.trim().toLowerCase() : '';
const message = typeof args?.message === 'string' ? args.message.trim() : '';
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
if (!to || !message)
throw makeError('to and message are required');
const signed = await signMessage(config, to, message);
const metadata = buildSignedIdentityMetadata(config, signed);
const extensions = metadata ? extensionsFromMeta(metadata) : undefined;
const rpc = buildRpcEnvelope(Method.SEND_MESSAGE, {
message: buildMessage(Role.USER, message, {
messageId: randomUUID(),
...(metadata ? { metadata } : {}),
...(extensions?.length ? { extensions } : {}),
}),
...(taskId ? { taskId } : {}),
}, randomUUID());
const raw = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(to)}`), {
method: 'POST',
apiKey: config.apiKey,
body: rpc,
});
if (raw.error)
throw makeError(raw.error.message);
const state = raw.result?.status?.state ? normalizeState(raw.result.status.state) : '';
return toTextResult({
taskId: raw.result?.id,
status: state || raw.result?.status?.state,
statusLabel: state ? (TaskStateLabel[state] || PlatformStateLabel[state] || state) : undefined,
to,
verified: Boolean(signed),
});
}
if (name === 'agenttrust_inbox') {
requireConfigured(config, name, { requireSlug: true });
const status = typeof args?.status === 'string' ? normalizeStatusFilterArg(args.status) : '';
const turn = typeof args?.turn === 'string' ? args.turn.trim() : '';
const limit = typeof args?.limit === 'number' ? Math.max(1, Math.min(100, Math.floor(args.limit))) : 20;
const url = new URL(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox`));
url.searchParams.set('limit', String(limit));
if (status)
url.searchParams.set('status', status);
if (turn)
url.searchParams.set('turn', turn);
const payload = await apiRequest(url.toString(), {
method: 'GET',
apiKey: config.apiKey,
});
const tasks = (payload.tasks || []).map((task) => {
const fromSlug = task.from.slug || 'unknown';
const fromName = task.from.name || 'Unknown sender';
const org = task.from.orgName ? `, ${task.from.orgName}` : '';
const badge = task.from.verified ? 'Verified' : (task.from.anonymous ? 'Unknown sender' : 'Unverified');
const normalizedState = normalizeState(task.status.state);
return {
taskId: task.id,
from: `${fromSlug} (${fromName}${org}) ${badge}`,
status: normalizedState,
statusLabel: TaskStateLabel[normalizedState] || PlatformStateLabel[normalizedState] || task.status.state,
turn: task.turn || null,
preview: task.lastMessage?.parts?.[0]?.text || '',
messages: task.messageCount,
receivedAt: task.createdAt,
};
});
return toTextResult({ tasks, total: payload.total ?? tasks.length });
}
if (name === 'agenttrust_reply') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
const message = typeof args?.message === 'string' ? args.message.trim() : '';
const status = typeof args?.status === 'string' ? normalizeStatusFilterArg(args.status) : '';
if (!taskId || !message)
throw makeError('taskId and message are required');
const context = await getTaskContext(config, taskId);
const recipientSlug = resolveCounterpartySlug(context, config.slug || '');
const signed = await signMessage(config, recipientSlug, message);
const metadata = buildSignedIdentityMetadata(config, signed);
const result = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}/reply`), {
method: 'POST',
apiKey: config.apiKey,
body: {
message,
...(status ? { status } : {}),
...(metadata ? { metadata } : {}),
...(signed ? signed : {}),
},
});
return toTextResult({
...result,
verified: Boolean(signed),
});
}
if (name === 'agenttrust_comment') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
const text = typeof args?.text === 'string' ? args.text.trim() : '';
const internal = args?.internal === true;
if (!taskId || !text)
throw makeError('taskId and text are required');
const context = await getTaskContext(config, taskId);
const recipientSlug = resolveCounterpartySlug(context, config.slug || '');
const signed = await signMessage(config, recipientSlug, text);
const metadata = buildSignedIdentityMetadata(config, signed);
const result = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}/reply`), {
method: 'POST',
apiKey: config.apiKey,
body: {
message: text,
comment: true,
...(internal ? { internal: true } : {}),
...(metadata ? { metadata } : {}),
...(signed ? signed : {}),
},
});
return toTextResult({
...result,
internal,
verified: Boolean(signed),
});
}
if (name === 'agenttrust_escalate') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
const reason = typeof args?.reason === 'string' ? args.reason.trim() : '';
if (!taskId || !reason)
throw makeError('taskId and reason are required');
const context = await getTaskContext(config, taskId);
const recipientSlug = resolveCounterpartySlug(context, config.slug || '');
const signed = await signMessage(config, recipientSlug, reason);
const metadata = buildSignedIdentityMetadata(config, signed);
const result = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}/reply`), {
method: 'POST',
apiKey: config.apiKey,
body: {
message: reason,
comment: true,
escalate: true,
...(metadata ? { metadata } : {}),
...(signed ? signed : {}),
},
});
return toTextResult({
...result,
verified: Boolean(signed),
});
}
if (name === 'agenttrust_context') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
const mode = typeof args?.mode === 'string' ? args.mode.trim().toLowerCase() : 'light';
if (!taskId)
throw makeError('taskId is required');
const payload = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}`), {
method: 'GET',
apiKey: config.apiKey,
});
if (mode === 'full')
return toTextResult(payload);
const status = typeof payload.status?.state === 'string'
? String(payload.status.state)
: 'unknown';
const history = Array.isArray(payload.history) ? payload.history : [];
const lines = [`Task: ${taskId} | Status: ${status}`, `Messages: ${history.length}`, ''];
history.forEach((entry, idx) => {
const role = typeof entry.role === 'string' ? entry.role : 'unknown';
const parts = Array.isArray(entry.parts) ? entry.parts : [];
const textParts = [];
const fileParts = [];
for (const part of parts) {
if (typeof part.text === 'string' && part.text) {
textParts.push(part.text);
],
}));
srv.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const config = await loadRuntimeConfig();
if (name === 'agenttrust_guard') {
requireConfigured(config, name);
const text = typeof args?.text === 'string' ? args.text : '';
if (!text.trim())
throw makeError('text is required');
const capabilities = Array.isArray(args?.capabilities) ? args.capabilities : [];
const result = await apiRequest(safeUrl(config.endpoint, '/api/guard'), {
method: 'POST',
apiKey: config.apiKey,
body: { text, capabilities },
});
return toTextResult(result);
}
if (name === 'agenttrust_issue_code') {
requireConfigured(config, name);
const payload = typeof args?.payload === 'string' ? args.payload : '';
if (!payload.trim())
throw makeError('payload is required');
const expirationSeconds = typeof args?.expiration_seconds === 'number' ? args.expiration_seconds : 172800;
const result = await apiRequest(safeUrl(config.endpoint, '/api/issue'), {
method: 'POST',
apiKey: config.apiKey,
body: {
payload,
expiration_seconds: expirationSeconds,
},
});
return toTextResult(result);
}
if (name === 'agenttrust_verify_code') {
requireConfigured(config, name);
const code = typeof args?.code === 'string' ? args.code : '';
if (!code.trim())
throw makeError('code is required');
const result = await apiRequest(safeUrl(config.endpoint, '/api/verify'), {
method: 'POST',
apiKey: config.apiKey,
body: { code },
});
return toTextResult(result);
}
if (name === 'agenttrust_send') {
requireConfigured(config, name, { requireSlug: true });
const to = typeof args?.to === 'string' ? args.to.trim().toLowerCase() : '';
const message = typeof args?.message === 'string' ? args.message.trim() : '';
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
if (!to || !message)
throw makeError('to and message are required');
const signed = await signMessage(config, to, message);
const metadata = buildSignedIdentityMetadata(config, signed);
const extensions = metadata ? extensionsFromMeta(metadata) : undefined;
const rpc = buildRpcEnvelope(Method.SEND_MESSAGE, {
message: buildMessage(Role.USER, message, {
messageId: randomUUID(),
...(metadata ? { metadata } : {}),
...(extensions?.length ? { extensions } : {}),
}),
...(taskId ? { taskId } : {}),
}, randomUUID());
const raw = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(to)}`), {
method: 'POST',
apiKey: config.apiKey,
body: rpc,
});
if (raw.error)
throw makeError(raw.error.message);
const state = raw.result?.status?.state ? normalizeState(raw.result.status.state) : '';
return toTextResult({
taskId: raw.result?.id,
status: state || raw.result?.status?.state,
statusLabel: state ? (TaskStateLabel[state] || PlatformStateLabel[state] || state) : undefined,
to,
verified: Boolean(signed),
});
}
if (name === 'agenttrust_inbox') {
requireConfigured(config, name, { requireSlug: true });
const status = typeof args?.status === 'string' ? normalizeStatusFilterArg(args.status) : '';
const turn = typeof args?.turn === 'string' ? args.turn.trim() : '';
const limit = typeof args?.limit === 'number' ? Math.max(1, Math.min(100, Math.floor(args.limit))) : 20;
const url = new URL(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox`));
url.searchParams.set('limit', String(limit));
if (status)
url.searchParams.set('status', status);
if (turn)
url.searchParams.set('turn', turn);
const payload = await apiRequest(url.toString(), {
method: 'GET',
apiKey: config.apiKey,
});
const tasks = (payload.tasks || []).map((task) => {
const fromSlug = task.from.slug || 'unknown';
const fromName = task.from.name || 'Unknown sender';
const org = task.from.orgName ? `, ${task.from.orgName}` : '';
const badge = task.from.verified ? 'Verified' : (task.from.anonymous ? 'Unknown sender' : 'Unverified');
const normalizedState = normalizeState(task.status.state);
return {
taskId: task.id,
from: `${fromSlug} (${fromName}${org}) ${badge}`,
status: normalizedState,
statusLabel: TaskStateLabel[normalizedState] || PlatformStateLabel[normalizedState] || task.status.state,
turn: task.turn || null,
preview: task.lastMessage?.parts?.[0]?.text || '',
messages: task.messageCount,
receivedAt: task.createdAt,
};
});
return toTextResult({ tasks, total: payload.total ?? tasks.length });
}
if (name === 'agenttrust_reply') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
const message = typeof args?.message === 'string' ? args.message.trim() : '';
const status = typeof args?.status === 'string' ? normalizeStatusFilterArg(args.status) : '';
if (!taskId || !message)
throw makeError('taskId and message are required');
const context = await getTaskContext(config, taskId);
const recipientSlug = resolveCounterpartySlug(context, config.slug || '');
const signed = await signMessage(config, recipientSlug, message);
const metadata = buildSignedIdentityMetadata(config, signed);
const result = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}/reply`), {
method: 'POST',
apiKey: config.apiKey,
body: {
message,
...(status ? { status } : {}),
...(metadata ? { metadata } : {}),
...(signed ? signed : {}),
},
});
return toTextResult({
...result,
verified: Boolean(signed),
});
}
if (name === 'agenttrust_comment') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
const text = typeof args?.text === 'string' ? args.text.trim() : '';
const internal = args?.internal === true;
if (!taskId || !text)
throw makeError('taskId and text are required');
const context = await getTaskContext(config, taskId);
const recipientSlug = resolveCounterpartySlug(context, config.slug || '');
const signed = await signMessage(config, recipientSlug, text);
const metadata = buildSignedIdentityMetadata(config, signed);
const result = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}/reply`), {
method: 'POST',
apiKey: config.apiKey,
body: {
message: text,
comment: true,
...(internal ? { internal: true } : {}),
...(metadata ? { metadata } : {}),
...(signed ? signed : {}),
},
});
return toTextResult({
...result,
internal,
verified: Boolean(signed),
});
}
if (name === 'agenttrust_escalate') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
const reason = typeof args?.reason === 'string' ? args.reason.trim() : '';
if (!taskId || !reason)
throw makeError('taskId and reason are required');
const context = await getTaskContext(config, taskId);
const recipientSlug = resolveCounterpartySlug(context, config.slug || '');
const signed = await signMessage(config, recipientSlug, reason);
const metadata = buildSignedIdentityMetadata(config, signed);
const result = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}/reply`), {
method: 'POST',
apiKey: config.apiKey,
body: {
message: reason,
comment: true,
escalate: true,
...(metadata ? { metadata } : {}),
...(signed ? signed : {}),
},
});
return toTextResult({
...result,
verified: Boolean(signed),
});
}
if (name === 'agenttrust_context') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
const mode = typeof args?.mode === 'string' ? args.mode.trim().toLowerCase() : 'light';
if (!taskId)
throw makeError('taskId is required');
const payload = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}`), {
method: 'GET',
apiKey: config.apiKey,
});
if (mode === 'full')
return toTextResult(payload);
const status = typeof payload.status?.state === 'string'
? String(payload.status.state)
: 'unknown';
const history = Array.isArray(payload.history) ? payload.history : [];
const lines = [`Task: ${taskId} | Status: ${status}`, `Messages: ${history.length}`, ''];
history.forEach((entry, idx) => {
const role = typeof entry.role === 'string' ? entry.role : 'unknown';
const parts = Array.isArray(entry.parts) ? entry.parts : [];
const textParts = [];
const fileParts = [];
for (const part of parts) {
if (typeof part.text === 'string' && part.text) {
textParts.push(part.text);
}
else if (part.kind === 'file' && typeof part.file === 'object' && part.file !== null) {
const file = part.file;
const name = typeof file.name === 'string' ? file.name : 'unnamed';
const size = typeof file.size === 'number' ? `${file.size} bytes` : '';
const uri = typeof file.uri === 'string' ? file.uri : '';
fileParts.push(`📎 ${name}${size ? ` (${size})` : ''}${uri ? ` — ${uri}` : ''}`);
}
}
else if (part.kind === 'file' && typeof part.file === 'object' && part.file !== null) {
const file = part.file;
const name = typeof file.name === 'string' ? file.name : 'unnamed';
const size = typeof file.size === 'number' ? `${file.size} bytes` : '';
const uri = typeof file.uri === 'string' ? file.uri : '';
fileParts.push(`📎 ${name}${size ? ` (${size})` : ''}${uri ? ` — ${uri}` : ''}`);
}
}
lines.push(`[${idx + 1}] ${role}`);
if (textParts.length)
lines.push(` ${textParts.join('\n ')}`);
for (const fp of fileParts)
lines.push(` ${fp}`);
});
return toTextResult({ thread: lines.join('\n') });
lines.push(`[${idx + 1}] ${role}`);
if (textParts.length)
lines.push(` ${textParts.join('\n ')}`);
for (const fp of fileParts)
lines.push(` ${fp}`);
});
return toTextResult({ thread: lines.join('\n') });
}
if (name === 'agenttrust_discover') {
requireConfigured(config, name);
const query = typeof args?.query === 'string' ? args.query.trim() : '';
const url = new URL(safeUrl(config.endpoint, '/api/agents'));
if (query)
url.searchParams.set('q', query);
const result = await apiRequest(url.toString(), {
method: 'GET',
apiKey: config.apiKey,
});
return toTextResult(result);
}
if (name === 'agenttrust_status') {
requireConfigured(config, name, { requireSlug: true });
const inboxUrl = new URL(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox`));
inboxUrl.searchParams.set('turn', config.slug || '');
inboxUrl.searchParams.set('limit', '1');
const inbox = await apiRequest(inboxUrl.toString(), {
method: 'GET',
apiKey: config.apiKey,
});
const keyInfo = await apiRequest(safeUrl(config.endpoint, `/a/${encodeURIComponent(config.slug || '')}/key`), { method: 'GET', apiKey: config.apiKey }).catch(() => ({}));
const keyInfoRecord = keyInfo;
return toTextResult({
slug: config.slug,
agentId: config.agentId,
endpoint: safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}`),
keyStatus: keyInfoRecord.key_status || 'unknown',
keyExpires: keyInfoRecord.expires_at || null,
pendingTasks: inbox.total || 0,
});
}
if (name === 'agenttrust_cancel') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
if (!taskId)
throw makeError('taskId is required');
const result = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}/cancel`), {
method: 'POST',
apiKey: config.apiKey,
body: {},
});
return toTextResult(result);
}
if (name === 'agenttrust_allowlist') {
requireConfigured(config, name, { requireSlug: true });
const url = new URL(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/contacts`));
url.searchParams.set('limit', '100');
url.searchParams.set('offset', '0');
const result = await apiRequest(url.toString(), {
method: 'GET',
apiKey: config.apiKey,
});
const identity = await fetchWhoAmI(config.endpoint, config.apiKey || '');
const orgName = readString(identity?.org?.name) || 'Unknown';
const entries = (result.contacts || [])
.map((entry) => {
const slug = readString(entry.contact?.slug);
if (!slug)
return null;
const org = readString(entry.contact?.orgName) || 'Unknown';
const addedAt = toDateOnly(entry.createdAt) || toDateOnly(entry.updatedAt) || '';
return {
slug,
org,
addedAt,
};
})
.filter((entry) => Boolean(entry));
return toTextResult({
mode: 'allowlist',
scope: 'organisation',
org: orgName,
entries,
});
}
throw makeError(`Unknown tool: ${name}`);
}
if (name === 'agenttrust_discover') {
requireConfigured(config, name);
const query = typeof args?.query === 'string' ? args.query.trim() : '';
const url = new URL(safeUrl(config.endpoint, '/api/agents'));
if (query)
url.searchParams.set('q', query);
const result = await apiRequest(url.toString(), {
method: 'GET',
apiKey: config.apiKey,
});
return toTextResult(result);
catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
if (name === 'agenttrust_status') {
requireConfigured(config, name, { requireSlug: true });
const inboxUrl = new URL(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox`));
inboxUrl.searchParams.set('turn', config.slug || '');
inboxUrl.searchParams.set('limit', '1');
const inbox = await apiRequest(inboxUrl.toString(), {
method: 'GET',
apiKey: config.apiKey,
});
const keyInfo = await apiRequest(safeUrl(config.endpoint, `/a/${encodeURIComponent(config.slug || '')}/key`), { method: 'GET', apiKey: config.apiKey }).catch(() => ({}));
const keyInfoRecord = keyInfo;
return toTextResult({
slug: config.slug,
agentId: config.agentId,
endpoint: safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}`),
keyStatus: keyInfoRecord.key_status || 'unknown',
keyExpires: keyInfoRecord.expires_at || null,
pendingTasks: inbox.total || 0,
});
}
if (name === 'agenttrust_cancel') {
requireConfigured(config, name, { requireSlug: true });
const taskId = typeof args?.taskId === 'string' ? args.taskId.trim() : '';
if (!taskId)
throw makeError('taskId is required');
const result = await apiRequest(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/inbox/${encodeURIComponent(taskId)}/cancel`), {
method: 'POST',
apiKey: config.apiKey,
body: {},
});
return toTextResult(result);
}
if (name === 'agenttrust_allowlist') {
requireConfigured(config, name, { requireSlug: true });
const url = new URL(safeUrl(config.endpoint, `/r/${encodeURIComponent(config.slug || '')}/contacts`));
url.searchParams.set('limit', '100');
url.searchParams.set('offset', '0');
const result = await apiRequest(url.toString(), {
method: 'GET',
apiKey: config.apiKey,
});
const identity = await fetchWhoAmI(config.endpoint, config.apiKey || '');
const orgName = readString(identity?.org?.name) || 'Unknown';
const entries = (result.contacts || [])
.map((entry) => {
const slug = readString(entry.contact?.slug);
if (!slug)
return null;
const org = readString(entry.contact?.orgName) || 'Unknown';
const addedAt = toDateOnly(entry.createdAt) || toDateOnly(entry.updatedAt) || '';
return {
slug,
org,
addedAt,
};
})
.filter((entry) => Boolean(entry));
return toTextResult({
mode: 'allowlist',
scope: 'organisation',
org: orgName,
entries,
});
}
throw makeError(`Unknown tool: ${name}`);
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
});
return srv;
}
// Global server instance for stdio mode
const server = createServer();
function printUsage() {

@@ -972,5 +979,6 @@ console.log('Usage:');

console.log(' agenttrust-mcp --regen-keys # rotate signing key');
console.log(' agenttrust-mcp # run MCP stdio server');
console.log(' agenttrust-mcp --http [port] # run MCP HTTP server (default port 3000)');
console.log(' agenttrust-mcp # run MCP stdio server (default)');
}
async function runStdioServer() {
async function ensureConfigReady() {
const config = await loadRuntimeConfig();

@@ -983,4 +991,6 @@ if (!config.apiKey) {

}
// Ensure signing capability is ready up front when identity is known.
await getOrCreateSigningKey(config);
}
async function runStdioServer() {
await ensureConfigReady();
const transport = new StdioServerTransport();

@@ -990,2 +1000,51 @@ await server.connect(transport);

}
async function runHttpServer(port) {
await ensureConfigReady();
const app = express();
// Do NOT use express.json() globally — StreamableHTTPServerTransport parses its own body
// Map of active transports by session ID
const transports = new Map();
// Handle all MCP requests — POST (messages), GET (SSE streams), DELETE (cleanup)
app.all('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
// Existing session — route to its transport
if (sessionId && transports.has(sessionId)) {
const transport = transports.get(sessionId);
await transport.handleRequest(req, res);
if (req.method === 'DELETE')
transports.delete(sessionId);
return;
}
// No session — only POST allowed (for initialize)
if (req.method !== 'POST') {
res.status(400).json({ error: 'Invalid or missing session ID' });
return;
}
// New session — create transport, connect server, let transport handle the request
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
transport.onclose = () => {
const sid = transport.sessionId;
if (sid)
transports.delete(sid);
};
const sessionServer = createServer();
await sessionServer.connect(transport);
// Let transport handle the request — it will assign session ID internally
await transport.handleRequest(req, res);
// Store transport after handling (session ID is now set)
if (transport.sessionId) {
transports.set(transport.sessionId, transport);
}
});
// Health check
app.get('/health', (_req, res) => {
res.json({ status: 'ok', sessions: transports.size });
});
app.listen(port, '0.0.0.0', () => {
console.log(`AgentTrust MCP server running on http://0.0.0.0:${port}/mcp`);
console.log(`Health check: http://0.0.0.0:${port}/health`);
});
}
async function main() {

@@ -1009,2 +1068,7 @@ const args = process.argv.slice(2);

}
if (cmd === '--http') {
const port = parseInt(args[1], 10) || 3000;
await runHttpServer(port);
return;
}
if (cmd === '--help' || cmd === '-h') {

@@ -1011,0 +1075,0 @@ printUsage();

{
"name": "@agenttrust/mcp-server",
"version": "1.0.0",
"version": "1.1.0",
"description": "MCP server for AgentTrust — A2A communication, agent identity, HITL escalation, and prompt injection detection",

@@ -49,2 +49,3 @@ "type": "module",

"@modelcontextprotocol/sdk": "^1.27.1",
"express": "^4.21.0",
"node-fetch": "^3.3.2",

@@ -54,2 +55,3 @@ "tweetnacl": "^1.0.3"

"devDependencies": {
"@types/express": "^4.17.25",
"@types/node": "^20.11.0",

@@ -56,0 +58,0 @@ "typescript": "^5.3.3"