import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { RefreshCredentialInputSchema } from '../schemas/toolSchemas.js';
import { validateAndSanitizeInput, createValidationErrorResult } from '../utils/validation.js';
/**
* Refresh an expiring or expired credential using W3C Credential Refresh specification
* Supports automatic refresh service discovery and manual refresh endpoints
*/
export async function refreshCredential(args: any): Promise<CallToolResult> {
// Validate and sanitize input
const validation = validateAndSanitizeInput(RefreshCredentialInputSchema, args, 'refresh_credential');
if (!validation.success) {
return createValidationErrorResult(validation.error!);
}
const data = validation.data!;
const { credentialId, refreshService } = data;
const HIVEAUTH_API_BASE_URL = process.env.HIVEAUTH_API_BASE_URL || 'http://localhost:3000';
const REFRESH_ENDPOINT = `${HIVEAUTH_API_BASE_URL}/api/refresh`;
try {
console.log(`[RefreshCredential] Starting credential refresh for: ${credentialId}`);
const payload = {
credentialId,
...(refreshService && { refreshService })
};
const response = await fetch(REFRESH_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(`Failed to refresh credential: ${errorData.message}`);
}
const result = await response.json();
// Analyze the refresh operation
const oldCredential = result.originalCredential;
const newCredential = result.refreshedCredential;
const refreshInfo = result.refreshInfo || {};
const summary = [
`π **Credential Refresh Results**`,
``,
`β’ Original Credential ID: ${credentialId}`,
`β’ New Credential ID: ${newCredential?.id || 'Generated automatically'}`,
`β’ Refresh Method: ${refreshInfo.method || 'W3C Credential Refresh'}`,
`β’ Refresh Service: ${refreshInfo.serviceEndpoint || refreshService || 'Auto-discovered'}`,
`β’ Refresh Status: ${result.success ? 'β
Successful' : 'β Failed'}`,
``
];
if (result.success && oldCredential && newCredential) {
// Compare expiration dates
const oldExpiry = oldCredential.expirationDate || oldCredential.validUntil;
const newExpiry = newCredential.expirationDate || newCredential.validUntil;
summary.push(`**π
Validity Period Comparison:**`);
if (oldExpiry) {
summary.push(`β’ Original Expiry: ${oldExpiry}`);
const wasExpired = new Date(oldExpiry) < new Date();
summary.push(`β’ Original Status: ${wasExpired ? 'β Expired' : 'β οΈ Expiring soon'}`);
} else {
summary.push(`β’ Original Expiry: No expiration date set`);
}
if (newExpiry) {
summary.push(`β’ New Expiry: ${newExpiry}`);
const timeUntilExpiry = new Date(newExpiry).getTime() - new Date().getTime();
const daysUntilExpiry = Math.ceil(timeUntilExpiry / (1000 * 60 * 60 * 24));
summary.push(`β’ New Status: β
Valid for ${daysUntilExpiry} more days`);
} else {
summary.push(`β’ New Expiry: No expiration date (indefinite validity)`);
}
summary.push(``);
// Compare credential content
summary.push(`**π Content Comparison:**`);
const contentChanges = compareCredentialContent(oldCredential, newCredential);
if (contentChanges.length === 0) {
summary.push(`β’ Content: β
Unchanged (only validity period updated)`);
} else {
summary.push(`β’ Content: β οΈ ${contentChanges.length} changes detected`);
contentChanges.forEach((change, index) => {
summary.push(` ${index + 1}. ${change}`);
});
}
summary.push(``);
// Issuer and signature information
summary.push(`**π Security Information:**`);
summary.push(`β’ Original Issuer: ${getIssuerInfo(oldCredential)}`);
summary.push(`β’ New Issuer: ${getIssuerInfo(newCredential)}`);
summary.push(`β’ Issuer Consistency: ${getIssuerInfo(oldCredential) === getIssuerInfo(newCredential) ? 'β
Same issuer' : 'β οΈ Different issuer'}`);
if (newCredential.proof) {
summary.push(`β’ New Signature: β
Fresh cryptographic signature`);
summary.push(`β’ Proof Type: ${newCredential.proof.type || 'Unknown'}`);
}
summary.push(``);
// Refresh service information
if (refreshInfo.serviceDiscovered) {
summary.push(`**π Service Discovery:**`);
summary.push(`β’ Service Discovery: β
Automatic discovery successful`);
summary.push(`β’ Discovered Endpoint: ${refreshInfo.serviceEndpoint}`);
summary.push(`β’ Discovery Method: ${refreshInfo.discoveryMethod || 'DID Document'}`);
summary.push(``);
}
// Chain of trust
if (result.trustChain) {
summary.push(`**π Trust Chain:**`);
summary.push(`β’ Original credential linked: β
Cryptographic reference maintained`);
summary.push(`β’ Chain integrity: ${result.trustChain.valid ? 'β
Valid' : 'β Broken'}`);
summary.push(`β’ Refresh authorization: ${result.trustChain.authorized ? 'β
Authorized' : 'β Unauthorized'}`);
summary.push(``);
}
} else if (!result.success) {
summary.push(`**β Refresh Failed:**`);
summary.push(`β’ Reason: ${result.error || 'Unknown error'}`);
if (result.retryable) {
summary.push(`β’ Retry Possible: β
Yes, try again later`);
summary.push(`β’ Suggested Retry: ${result.retryAfter || 'No specific time suggested'}`);
} else {
summary.push(`β’ Retry Possible: β No, manual intervention required`);
}
summary.push(``);
}
// Usage recommendations
summary.push(`**π‘ Next Steps:**`);
if (result.success) {
summary.push(`β’ Replace the original credential with the refreshed version`);
summary.push(`β’ Update any stored references to use the new credential ID`);
summary.push(`β’ Verify the refreshed credential independently`);
summary.push(`β’ Set up monitoring for the new expiration date`);
if (result.originalCredential) {
summary.push(`β’ Consider revoking the original credential if policy requires it`);
}
} else {
summary.push(`β’ Check credential refresh service availability`);
summary.push(`β’ Verify the credential is eligible for refresh`);
summary.push(`β’ Contact the issuer if automatic refresh fails`);
summary.push(`β’ Consider manual credential renewal process`);
}
return {
content: [
{
type: 'text',
text: summary.join('\n')
},
{
type: 'text',
text: `\`\`\`json\n${JSON.stringify({
refreshSummary: {
credentialId,
success: result.success,
refreshMethod: refreshInfo.method,
serviceEndpoint: refreshInfo.serviceEndpoint,
originalExpiry: oldCredential?.expirationDate || oldCredential?.validUntil,
newExpiry: newCredential?.expirationDate || newCredential?.validUntil,
contentChanges: result.success ? compareCredentialContent(oldCredential, newCredential).length : 0
},
refreshedCredential: result.success ? newCredential : null,
refreshInfo: refreshInfo
}, null, 2)}\n\`\`\``
}
]
};
} catch (error: any) {
// Check if it's a network error (HiveAuth API not available)
if (error.message.includes('fetch failed') || error.message.includes('ECONNREFUSED')) {
return {
content: [
{
type: 'text',
text: `π **Credential Refresh (Simulation Mode)**\n\n` +
`Since the HiveAuth API is not available, here's what the credential refresh would accomplish:\n\n` +
`**Input Analysis:**\n` +
`β’ Credential ID: ${credentialId}\n` +
`β’ Refresh Service: ${refreshService || 'Auto-discovery from credential'}\n\n` +
`**Expected Refresh Process:**\n` +
`β’ Locate credential by ID in the system\n` +
`β’ Check expiration status and refresh eligibility\n` +
`β’ Discover or use specified refresh service endpoint\n` +
`β’ Request new credential with extended validity period\n` +
`β’ Maintain credential content while updating temporal properties\n` +
`β’ Generate fresh cryptographic signatures\n\n` +
`**W3C Credential Refresh Benefits:**\n` +
`β’ Automated credential lifecycle management\n` +
`β’ Seamless validity period extension\n` +
`β’ Maintains trust chain and cryptographic integrity\n` +
`β’ Reduces manual intervention for routine renewals\n\n` +
`**To enable full refresh:** Ensure HiveAuth API is running at ${HIVEAUTH_API_BASE_URL}`
}
]
};
}
return {
content: [
{
type: 'text',
text: `Failed to refresh credential: ${error.message}`
}
],
isError: true
};
}
}
/**
* Compare two credentials and return list of changes
*/
function compareCredentialContent(oldCred: any, newCred: any): string[] {
const changes: string[] = [];
if (!oldCred || !newCred) return changes;
// Compare credential subject
if (JSON.stringify(oldCred.credentialSubject) !== JSON.stringify(newCred.credentialSubject)) {
changes.push('Credential subject data modified');
}
// Compare types
if (JSON.stringify(oldCred.type) !== JSON.stringify(newCred.type)) {
changes.push('Credential types changed');
}
// Compare issuer
if (getIssuerInfo(oldCred) !== getIssuerInfo(newCred)) {
changes.push('Issuer information changed');
}
// Compare context
if (JSON.stringify(oldCred['@context']) !== JSON.stringify(newCred['@context'])) {
changes.push('JSON-LD context updated');
}
return changes;
}
/**
* Extract issuer information from credential
*/
function getIssuerInfo(credential: any): string {
if (!credential?.issuer) return 'Unknown';
if (typeof credential.issuer === 'string') {
return credential.issuer;
}
return credential.issuer.id || credential.issuer.name || 'Unknown';
}