From 7b3321b5bd4b58e6149e1de5ec04e824fd08914d Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Mon, 2 Feb 2026 13:33:42 +0530 Subject: [PATCH 01/10] feat: Add mcp-tookit-studio --- packages/studio/CHANGELOG.md | 151 ++++++++++++++ packages/studio/package.json | 45 ++++ packages/studio/src/agent.ts | 134 ++++++++++++ packages/studio/src/index.ts | 66 ++++++ packages/studio/src/tools/did.ts | 192 ++++++++++++++++++ packages/studio/src/tools/index.ts | 1 + packages/studio/src/types.ts | 316 +++++++++++++++++++++++++++++ packages/studio/tsconfig.json | 8 + pnpm-lock.yaml | 22 +- 9 files changed, 924 insertions(+), 11 deletions(-) create mode 100644 packages/studio/CHANGELOG.md create mode 100644 packages/studio/package.json create mode 100644 packages/studio/src/agent.ts create mode 100644 packages/studio/src/index.ts create mode 100644 packages/studio/src/tools/did.ts create mode 100644 packages/studio/src/tools/index.ts create mode 100644 packages/studio/src/types.ts create mode 100644 packages/studio/tsconfig.json diff --git a/packages/studio/CHANGELOG.md b/packages/studio/CHANGELOG.md new file mode 100644 index 0000000..b4a9dc5 --- /dev/null +++ b/packages/studio/CHANGELOG.md @@ -0,0 +1,151 @@ +# Changelog + +# @cheqd/mcp-toolkit-credo [1.5.0](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.4.0...@cheqd/mcp-toolkit-credo@1.5.0) (2025-06-12) + + +### Features + +* Streaming http support [DEV-5079] ([#43](https://github.com/cheqd/mcp-toolkit/issues/43)) ([820245b](https://github.com/cheqd/mcp-toolkit/commit/820245b459ef246efa7a73c1670644aa43eefd38)), closes [#41](https://github.com/cheqd/mcp-toolkit/issues/41) + +# @cheqd/mcp-toolkit-credo [1.5.0-develop.1](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.4.0...@cheqd/mcp-toolkit-credo@1.5.0-develop.1) (2025-06-11) + + +### Features + +* Streaming http support [DEV-5079] ([#43](https://github.com/cheqd/mcp-toolkit/issues/43)) ([820245b](https://github.com/cheqd/mcp-toolkit/commit/820245b459ef246efa7a73c1670644aa43eefd38)), closes [#41](https://github.com/cheqd/mcp-toolkit/issues/41) + +# @cheqd/mcp-toolkit-credo [1.4.0](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.3.0...@cheqd/mcp-toolkit-credo@1.4.0) (2025-05-15) + + +### Features + +* Add whois prompt [DEV-4935] ([#31](https://github.com/cheqd/mcp-toolkit/issues/31)) ([e051ad8](https://github.com/cheqd/mcp-toolkit/commit/e051ad807122acaaa51f2a38e147cd8e10642460)) + +# @cheqd/mcp-toolkit-credo [1.4.0-develop.1](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.3.0...@cheqd/mcp-toolkit-credo@1.4.0-develop.1) (2025-05-15) + + +### Features + +* Add whois prompt [DEV-4935] ([#31](https://github.com/cheqd/mcp-toolkit/issues/31)) ([e051ad8](https://github.com/cheqd/mcp-toolkit/commit/e051ad807122acaaa51f2a38e147cd8e10642460)) + +# @cheqd/mcp-toolkit-credo [1.3.0](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.2.0...@cheqd/mcp-toolkit-credo@1.3.0) (2025-05-12) + + +### Features + +* Add trust registry toolkit [DEV-4896] ([#15](https://github.com/cheqd/mcp-toolkit/issues/15)) ([ba98518](https://github.com/cheqd/mcp-toolkit/commit/ba98518bf0fb14d8df94413c211398ddf4169683)) + +# @cheqd/mcp-toolkit-credo [1.3.0-develop.1](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.2.0...@cheqd/mcp-toolkit-credo@1.3.0-develop.1) (2025-04-30) + + +### Features + +* Add trust registry toolkit [DEV-4896] ([#15](https://github.com/cheqd/mcp-toolkit/issues/15)) ([ba98518](https://github.com/cheqd/mcp-toolkit/commit/ba98518bf0fb14d8df94413c211398ddf4169683)) + +# @cheqd/mcp-toolkit-credo [1.2.0](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.1.0...@cheqd/mcp-toolkit-credo@1.2.0) (2025-04-30) + + +### Features + +* add resources and prompts [DEV-4862] ([#20](https://github.com/cheqd/mcp-toolkit/issues/20)) ([40e3d0e](https://github.com/cheqd/mcp-toolkit/commit/40e3d0e95b838470aa0b3376ee194daa53f80946)) + +# @cheqd/mcp-toolkit-credo [1.2.0-develop.1](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.1.0...@cheqd/mcp-toolkit-credo@1.2.0-develop.1) (2025-04-30) + + +### Features + +* add resources and prompts [DEV-4862] ([#20](https://github.com/cheqd/mcp-toolkit/issues/20)) ([40e3d0e](https://github.com/cheqd/mcp-toolkit/commit/40e3d0e95b838470aa0b3376ee194daa53f80946)) + +# @cheqd/mcp-toolkit-credo [1.1.0-develop.3](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.1.0-develop.2...@cheqd/mcp-toolkit-credo@1.1.0-develop.3) (2025-04-26) + +# @cheqd/mcp-toolkit-credo [1.1.0](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.0.0...@cheqd/mcp-toolkit-credo@1.1.0) (2025-04-30) + +### Features + +* add resources and prompts [DEV-4862] ([#20](https://github.com/cheqd/mcp-toolkit/issues/20)) ([40e3d0e](https://github.com/cheqd/mcp-toolkit/commit/40e3d0e95b838470aa0b3376ee194daa53f80946)) +* Add Import credential tool ([#16](https://github.com/cheqd/mcp-toolkit/issues/16)) ([58c0d3b](https://github.com/cheqd/mcp-toolkit/commit/58c0d3b2d03e6eaeb1641416e7d72975239c9c78)) +* Add Playwright e2e tests [DEV-4863] ([#14](https://github.com/cheqd/mcp-toolkit/issues/14)) ([7b683ba](https://github.com/cheqd/mcp-toolkit/commit/7b683ba9b97bda47ace0e77ca1c96b927aa37ac3)) + +# @cheqd/mcp-toolkit-credo [1.1.0-develop.2](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.1.0-develop.1...@cheqd/mcp-toolkit-credo@1.1.0-develop.2) (2025-04-10) + + +### Features + +* Add Playwright e2e tests [DEV-4863] ([#14](https://github.com/cheqd/mcp-toolkit/issues/14)) ([7b683ba](https://github.com/cheqd/mcp-toolkit/commit/7b683ba9b97bda47ace0e77ca1c96b927aa37ac3)) + +# @cheqd/mcp-toolkit-credo [1.1.0-develop.1](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.0.0...@cheqd/mcp-toolkit-credo@1.1.0-develop.1) (2025-04-10) + + +### Features + +* Add Import credential tool ([#16](https://github.com/cheqd/mcp-toolkit/issues/16)) ([58c0d3b](https://github.com/cheqd/mcp-toolkit/commit/58c0d3b2d03e6eaeb1641416e7d72975239c9c78)) + +# @cheqd/mcp-toolkit-credo 1.0.0 (2025-03-28) + + +### Bug Fixes + +* Add format script ([dd52a22](https://github.com/cheqd/mcp-toolkit/commit/dd52a22b491a50e8ebbfb37e0c80bffe08082d4e)) +* Add ICredoToolkit options ([6597491](https://github.com/cheqd/mcp-toolkit/commit/6597491350acb91dea1a94f8a537fc5f44cc98e2)) +* addressed review comments ([62e5609](https://github.com/cheqd/mcp-toolkit/commit/62e56096c211d3d52bfa2dd77183ed5793425279)) +* double start and QR code ([4fa489f](https://github.com/cheqd/mcp-toolkit/commit/4fa489f75103c6e45209b230533ec11d00f22307)) +* package names ([ce71fa9](https://github.com/cheqd/mcp-toolkit/commit/ce71fa98fcc6941a81f6d5b83866513fd0712458)) +* Register inbound and outbound ts ([cf7fe41](https://github.com/cheqd/mcp-toolkit/commit/cf7fe41f3572f3c519757a344686e22cac7ba3bc)) +* Update docker-compose and demo files [DEV-4851] ([#7](https://github.com/cheqd/mcp-toolkit/issues/7)) ([fc298d6](https://github.com/cheqd/mcp-toolkit/commit/fc298d61f66d1f731486900915c5e16530f5b2d9)) + + +### Features + +* Add connections toolkit in credo ([ff79f01](https://github.com/cheqd/mcp-toolkit/commit/ff79f01e2d5aee2aadfe4c7d065fad3fb58e7d2e)) +* Add credential tool handlers ([98f9ffb](https://github.com/cheqd/mcp-toolkit/commit/98f9ffba6c1cbe51e1e4964038c51aed2ee99714)) +* Add credo proof tools [DEV-4814] ([#9](https://github.com/cheqd/mcp-toolkit/issues/9)) ([7c141c1](https://github.com/cheqd/mcp-toolkit/commit/7c141c1bda69e0551d7cf4e5fcecd208bef48633)) +* Add list anoncreds tools ([91e768d](https://github.com/cheqd/mcp-toolkit/commit/91e768dcd281d54ad81ae2b3a0713c05918653bd)) +* Init MCP Toolkit ([98ab4c1](https://github.com/cheqd/mcp-toolkit/commit/98ab4c1385507c23a285f0a4ae64ff958ac91625)) +* Update did toolkit && fix connection in credo ([d560e5a](https://github.com/cheqd/mcp-toolkit/commit/d560e5a6d5505ee44f92b0e07ab1f3adf42f0c28)) + +# @cheqd/mcp-toolkit-credo [1.0.0-develop.5](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.0.0-develop.4...@cheqd/mcp-toolkit-credo@1.0.0-develop.5) (2025-03-28) + + +### Bug Fixes + +* Update docker-compose and demo files [DEV-4851] ([#7](https://github.com/cheqd/mcp-toolkit/issues/7)) ([fc298d6](https://github.com/cheqd/mcp-toolkit/commit/fc298d61f66d1f731486900915c5e16530f5b2d9)) + +# @cheqd/mcp-toolkit-credo [1.0.0-develop.4](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.0.0-develop.3...@cheqd/mcp-toolkit-credo@1.0.0-develop.4) (2025-03-28) + + +### Features + +* Add credo proof tools [DEV-4814] ([#9](https://github.com/cheqd/mcp-toolkit/issues/9)) ([7c141c1](https://github.com/cheqd/mcp-toolkit/commit/7c141c1bda69e0551d7cf4e5fcecd208bef48633)) + +# @cheqd/mcp-toolkit-credo [1.0.0-develop.3](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.0.0-develop.2...@cheqd/mcp-toolkit-credo@1.0.0-develop.3) (2025-03-27) + + +### Features + +* Update did toolkit && fix connection in credo ([d560e5a](https://github.com/cheqd/mcp-toolkit/commit/d560e5a6d5505ee44f92b0e07ab1f3adf42f0c28)) + +# @cheqd/mcp-toolkit-credo [1.0.0-develop.2](https://github.com/cheqd/mcp-toolkit/compare/@cheqd/mcp-toolkit-credo@1.0.0-develop.1...@cheqd/mcp-toolkit-credo@1.0.0-develop.2) (2025-03-26) + + +## Bug Fixes + +* addressed review comments ([62e5609](https://github.com/cheqd/mcp-toolkit/commit/62e56096c211d3d52bfa2dd77183ed5793425279)) +* double start and QR code ([4fa489f](https://github.com/cheqd/mcp-toolkit/commit/4fa489f75103c6e45209b230533ec11d00f22307)) + +# @cheqd/mcp-toolkit-credo 1.0.0-develop.1 (2025-03-20) + + +## Bug Fixes + +* Add format script ([dd52a22](https://github.com/cheqd/mcp-toolkit/commit/dd52a22b491a50e8ebbfb37e0c80bffe08082d4e)) +* Add ICredoToolkit options ([6597491](https://github.com/cheqd/mcp-toolkit/commit/6597491350acb91dea1a94f8a537fc5f44cc98e2)) +* package names ([ce71fa9](https://github.com/cheqd/mcp-toolkit/commit/ce71fa98fcc6941a81f6d5b83866513fd0712458)) +* Register inbound and outbound ts ([cf7fe41](https://github.com/cheqd/mcp-toolkit/commit/cf7fe41f3572f3c519757a344686e22cac7ba3bc)) + + +## Features + +* Add connections toolkit in credo ([ff79f01](https://github.com/cheqd/mcp-toolkit/commit/ff79f01e2d5aee2aadfe4c7d065fad3fb58e7d2e)) +* Add credential tool handlers ([98f9ffb](https://github.com/cheqd/mcp-toolkit/commit/98f9ffba6c1cbe51e1e4964038c51aed2ee99714)) +* Add list anoncreds tools ([91e768d](https://github.com/cheqd/mcp-toolkit/commit/91e768dcd281d54ad81ae2b3a0713c05918653bd)) +* Init MCP Toolkit ([98ab4c1](https://github.com/cheqd/mcp-toolkit/commit/98ab4c1385507c23a285f0a4ae64ff958ac91625)) diff --git a/packages/studio/package.json b/packages/studio/package.json new file mode 100644 index 0000000..9ab0bdc --- /dev/null +++ b/packages/studio/package.json @@ -0,0 +1,45 @@ +{ + "name": "@cheqd/mcp-toolkit-studio", + "version": "1.5.0", + "packageManager": "pnpm@9.1.3", + "description": "MCP Toolkit for cheqd studio (studio.cheqd.net)", + "author": "Cheqd Foundation Limited (https://github.com/cheqd)", + "license": "Apache-2.0", + "main": "./build/index.js", + "module": "./build/index.js", + "types": "./build/index.d.ts", + "type": "module", + "files": [ + "build" + ], + "scripts": { + "build": "rm -rf build && tsc && chmod +x build/*.js", + "start": "node build/index.js", + "watch": "tsc --watch", + "clean": "rm -rf ./build", + "format": "prettier --config ../../.prettierrc.json --write 'src/**/*.{js,ts,cjs,mjs,json}'", + "test": "jest", + "test:coverage": "jest --coverage" + }, + "keywords": [ + "cheqd", + "mcp-toolkit", + "mcp", + "cheqd-studio" + ], + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1", + "zod": "^3.25.56" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "typescript": "^5.8.3" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/packages/studio/src/agent.ts b/packages/studio/src/agent.ts new file mode 100644 index 0000000..a89c0e2 --- /dev/null +++ b/packages/studio/src/agent.ts @@ -0,0 +1,134 @@ +import { IStudioToolKitOptions, CreateDidDocumentResponse, UpdateDidDocumentParams, UpdateDidDocumentResponse, DeactivateDidDocumentParams, CreateDidDocumentRequestType, UpdateDidDocumentRequestType, DeactivateDidDocumentRequestType, CreateDidDocumentResponseType, UpdateDidDocumentResponseType, DeactivateDidDocumentResponseType, CreateDidLinkedResourceRequestType, CreateDidLinkedResourceResponseType } from './types.js'; +import { z } from 'zod' + +export class StudioAgent { + public name: string; + public endpoint: string; + + public constructor({ name }: IStudioToolKitOptions) { + this.name = name; + + if(!process.env.STUDIO_API_ENDPOINT) { + throw new Error("Studio Endpoint Not initialized") + } + + this.endpoint = process.env.STUDIO_API_ENDPOINT; + } + + /** + * Initialize the agent if needed + */ + public async initializeAgent() {} + + public dids = { + resolveDidDocument: async (did: string) => { + const response = await fetch(`${this.endpoint}/did/search/${did}`); + if (!response.ok) { + throw new Error(`Failed to resolve DID: ${response.statusText}`); + } + return await response.json(); + }, + + create: async(body: CreateDidDocumentRequestType): Promise => { + const response = await fetch(`${this.endpoint}/did/create`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to create DID: ${response.statusText}`); + } + return await response.json(); + }, + + update: async(body: UpdateDidDocumentRequestType): Promise => { + const response = await fetch(`${this.endpoint}/did/update`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to create DID: ${response.statusText}`); + } + return await response.json(); + }, + + deactivate: async(body: DeactivateDidDocumentRequestType): Promise => { + const response = await fetch(`${this.endpoint}/did/deactivate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to create DID: ${response.statusText}`); + } + return await response.json(); + }, + + getCreatedDids: async(): Promise => { + const response = await fetch(`${this.endpoint}/did/list`); + if (!response.ok) { + throw new Error(`Failed to resolve DID: ${response.statusText}`); + } + return await response.json(); + } + }; + + public resources = { + createResource: async(body: CreateDidLinkedResourceRequestType): Promise => { + const response = await fetch(`${this.endpoint}/resource/create`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to create DID: ${response.statusText}`); + } + return await response.json(); + }, + + resolveResource: async (didUrl: string) => { + const response = await fetch(`${this.endpoint}/resource/search/${didUrl}`); + if (!response.ok) { + throw new Error(`Failed to resolve DID: ${response.statusText}`); + } + return await response.json(); + }, + } + + + public statusList = { + create: async() => { + + }, + + update: async() => { + + } + } + + + public credentials = { + + issue: async() => { + + }, + + verify: async() => { + + } + } + + public presentations = { + + create: async() => { + + }, + + verify: async() => { + + } + } + +} + diff --git a/packages/studio/src/index.ts b/packages/studio/src/index.ts new file mode 100644 index 0000000..8cf022e --- /dev/null +++ b/packages/studio/src/index.ts @@ -0,0 +1,66 @@ +import { StudioAgent } from './agent.js'; +import { + DidToolHandler, +} from './tools/index.js'; +import { IStudioToolKitOptions } from './types.js'; + +/** + * StudioToolKit provides a comprehensive set of tools for interacting with the Studio agent. + * It bundles together various handlers for managing DIDs, credentials, connections, and anonymous credentials. + */ +export class StudioToolKit { + studio: StudioAgent; + /** + * Creates a new StudioToolKit instance with the specified configuration. + * @param {IStudioToolKitOptions} options - Configuration options for the toolkit + * @param {string} options.name - Name of the agent + */ + constructor({ name }: IStudioToolKitOptions) { + this.studio = new StudioAgent({ name }) + } + + /// Initializes the Studio agent and prepares it for use. + /// This method must be called before using any tools or resources. + /// It sets up the agent's internal state and ensures that all necessary components are ready. + async init() { + await this.studio.initializeAgent(); + } + + /// Shuts down the Studio agent and cleans up any resources. + /// This method should be called when the agent is no longer needed. + async shutdown() { + } + + /** + * Returns an array of all available tools grouped by functionality: + * - DID Management Tools (resolve, create, update, deactivate DIDs and linked resources) + * - Anonymous Credentials Tools (schema and credential definition management) + * - Connection Management Tools (create invitations, accept connections, list and get records) + * - Credential Management Tools (connectionless and connection-based credential offers, list and get records) + * + * @returns {Promise} Array of tool definitions + */ + async getTools() { + return [ + new DidToolHandler(this.studio).resolveDidTool(), + new DidToolHandler(this.studio).createDidTool(), + new DidToolHandler(this.studio).updateDidTool(), + new DidToolHandler(this.studio).deactivateDidTool(), + new DidToolHandler(this.studio).listDidTool(), + new DidToolHandler(this.studio).createDIDLinkedResourceTool(), + new DidToolHandler(this.studio).resolveDIDLinkedResourceTool(), + ]; + } + /** + * Registers all resources with the MCP server + * @param server The MCP server instance + */ + registerResources(server: any) { + } + /** + * Registers all prompts with the MCP server + * @param server The MCP server instance + */ + registerPrompts(server: any) { + } +} diff --git a/packages/studio/src/tools/did.ts b/packages/studio/src/tools/did.ts new file mode 100644 index 0000000..d085f88 --- /dev/null +++ b/packages/studio/src/tools/did.ts @@ -0,0 +1,192 @@ +import { DidDocument, DidDocumentService } from '@credo-ts/core'; +import { StudioAgent } from '../agent.js'; +import { + CreateDidDocumentParams, + CreateDidLinkedResourceParams, + DeactivateDidDocumentParams, + ResolveDidDocumentParams, + ResolveDidLinkedResourceParams, + ToolDefinition, + UpdateDidDocumentParams, +} from '../types.js'; + +/** + * Handler class for managing Decentralized Identifiers (DIDs) in the Credo agent. + * Provides tools for creating, updating, deactivating, and resolving DIDs on the cheqd network. + */ +export class DidToolHandler { + studio: StudioAgent; + + constructor(studio: StudioAgent) { + this.studio = studio; + } + + /** + * Resolves a DID document and its metadata from the cheqd network. + * Returns the complete DID document with all its components. + */ + resolveDidTool(): ToolDefinition { + return { + name: 'resolve-did', + description: + 'Resolves a DID document and its associated metadata from the cheqd network. Returns the complete DID document including verification methods, services, and other components.', + schema: ResolveDidDocumentParams, + handler: async ({ did }) => { + const result = await this.studio.dids.resolveDidDocument(did); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; + } + + /** + * Creates and publishes a new DID document to the cheqd network. + * Generates a new DID with specified verification methods and services. + */ + createDidTool(): ToolDefinition { + return { + name: 'create-did', + description: + 'Creates and publishes a new DID document to the specified cheqd network (testnet or mainnet). Generates a new DID with default verification methods and returns the complete DID document.', + schema: CreateDidDocumentParams, + handler: async (body) => { + const result = await this.studio.dids.create(body); + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; + } + + /** + * Updates an existing DID document on the cheqd network. + * Modifies the DID document while maintaining its core identifier. + */ + updateDidTool(): ToolDefinition { + return { + name: 'update-did', + description: + "Updates an existing DID document on the cheqd network. Allows modification of verification methods, services, and other components while maintaining the DID's core identifier.", + schema: UpdateDidDocumentParams, + handler: async (body) => { + const result = await this.studio.dids.update(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; + } + + /** + * Deactivates a DID document on the cheqd network. + * Permanently marks the DID as inactive. + */ + deactivateDidTool(): ToolDefinition { + return { + name: 'deactivate-did', + description: + 'Deactivates a DID document on the cheqd network. Permanently marks the DID as inactive, preventing any further updates or usage.', + schema: DeactivateDidDocumentParams, + handler: async ({ did }) => { + const result = await this.studio.dids.deactivate({ did }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; + } + + listDidTool(): ToolDefinition<{}> { + return { + name: 'list-did', + description: 'List the DIDs from the wallet', + schema: {}, + handler: async () => { + const result = await this.studio.dids.getCreatedDids(); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; + } + + /** + * Resolves a DID-linked resource from the cheqd network. + * Retrieves a specific resource associated with a DID. + */ + resolveDIDLinkedResourceTool(): ToolDefinition { + return { + name: 'resolve-did-linked-resource', + description: + 'Resolves a specific resource linked to a DID on the cheqd network. Returns the resource data and its associated metadata.', + schema: ResolveDidLinkedResourceParams, + handler: async ({ didUrl }) => { + const result = await this.studio.resources.resolveResource(didUrl); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; + } + + /** + * Creates and publishes a DID-linked resource to the cheqd network. + * Associates a new resource with an existing DID. + */ + createDIDLinkedResourceTool(): ToolDefinition { + return { + name: 'create-did-linked-resource', + description: + 'Creates and publishes a new resource linked to an existing DID on the cheqd network. Associates the resource with the DID and makes it available for resolution.', + schema: CreateDidLinkedResourceParams, + handler: async (body) => { + const result = await this.studio.resources.createResource(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; + } +} diff --git a/packages/studio/src/tools/index.ts b/packages/studio/src/tools/index.ts new file mode 100644 index 0000000..4ec38b3 --- /dev/null +++ b/packages/studio/src/tools/index.ts @@ -0,0 +1 @@ +export * from './did.js'; diff --git a/packages/studio/src/types.ts b/packages/studio/src/types.ts new file mode 100644 index 0000000..f1e5279 --- /dev/null +++ b/packages/studio/src/types.ts @@ -0,0 +1,316 @@ +import { ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z, ZodRawShape } from 'zod'; + +/** + * Base interface for tool definitions in the Studio toolkit. + * Defines the structure for all tools with their name, description, schema, and handler. + */ +export interface ToolDefinition { + readonly name: string; + readonly description: string; + readonly schema: Args; + handler: ToolCallback; +} + +/** + * Configuration options for initializing the Studio toolkit. + */ +export interface IStudioToolKitOptions { + name: string; +} + +/** + * Schema for validating cheqd Decentralized Identifiers (DIDs). + * Must start with 'did:cheqd:' followed by network and unique identifier. + */ +const DID = z + .string() + .startsWith('did:cheqd:') + .describe( + 'A cheqd Decentralized Identifier (DID) in the format: did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009' + ); + +/** + * Schema for validating DID URLs that point to resources. + * Must be a valid cheqd DID followed by '/resources/'. + */ +export const DID_URL = z.string().startsWith('did:cheqd:').includes('/resources/'); + +// DID Document Management Parameters +export const CreateDidDocumentParams = { + network: z + .enum(['testnet', 'mainnet']) + .describe('The cheqd network to publish the DID document (testnet or mainnet)'), + identifierFormatType: z.enum(['uuid', 'base58btc']).optional().default('uuid').describe('Algorithm for generating the method-specific ID'), + verificationMethodType: z.enum(['Ed25519VerificationKey2018', 'JsonWebKey2020', 'Ed25519VerificationKey2020']).optional().default('Ed25519VerificationKey2020').describe('Type of verification method for the DID'), +}; + +const CreateDidDocumentShape = z.object(CreateDidDocumentParams) +export type CreateDidDocumentRequestType = z.infer; + +export const CreateDidDocumentResponse = z.object({ + did: z.string().describe('The DID'), + didDocument: z.any().describe('The full DID Document'), + success: z.boolean().describe('Whether the operation was successful'), + existedInDb: z.boolean().describe('Whether the DID was retrieved from local database'), + error: z.string().optional().describe('Error message if creation failed'), +}) +export type CreateDidDocumentResponseType = z.infer; + +/** + * JSON Web Key schema for cryptographic keys + */ +const JwkJsonSchema = z + .object({ + kty: z.string(), + use: z.optional(z.string()), + }) + .catchall(z.unknown()); + +/** + * Schema for DID verification methods + */ +const VerificationMethodSchema = z.object({ + id: z.string(), + type: z.string(), + controller: z.string(), + publicKeyJwk: z.optional(JwkJsonSchema), + publicKeyMultibase: z.optional(z.string()), +}); + +/** + * Schema for DID service endpoints + */ +const DidDocumentServiceSchema = z.object({ + id: z.string(), + type: z.string(), + serviceEndpoint: z.union([z.string(), z.array(z.string())]), +}); + +/** + * Complete schema for a DID Document + */ +const DidDocumentSchema = z.object({ + id: z.string(), + alsoKnownAs: z.optional(z.array(z.string())), + controller: z.optional(z.union([z.string(), z.array(z.string())])), + verificationMethod: z.optional(z.array(VerificationMethodSchema)), + service: z.optional(z.array(DidDocumentServiceSchema)), + authentication: z.optional(z.array(z.union([z.string(), VerificationMethodSchema]))), + assertionMethod: z.optional(z.array(z.union([z.string(), VerificationMethodSchema]))), + keyAgreement: z.optional(z.array(z.union([z.string(), VerificationMethodSchema]))), + capabilityInvocation: z.optional(z.array(z.union([z.string(), VerificationMethodSchema]))), + capabilityDelegation: z.optional(z.array(z.union([z.string(), VerificationMethodSchema]))), +}); + +export const UpdateDidDocumentParams = { + did: DID, + service: z.array(DidDocumentServiceSchema).optional(), + verificationMethod: z.array(VerificationMethodSchema).optional(), + authentication: z.array(z.string()).optional(), + publicKeyHexs: z.array(z.string()).optional().describe('List of key references (publicKeys) in hexadecimal format for signing'), + didDocument: DidDocumentSchema.optional(), +}; + +const UpdateDidDocumentShape = z.object(UpdateDidDocumentParams) +export type UpdateDidDocumentRequestType = z.infer; + +export const UpdateDidDocumentResponse = z.object({ + did: z.string().describe('The updated DID'), + didDocument: DidDocumentSchema, + success: z.boolean().describe('Whether the update was successful'), + error: z.string().optional().describe('Error message if update failed'), +}) +export type UpdateDidDocumentResponseType = z.infer; + +export const DeactivateDidDocumentParams = { + did: DID, +}; + +const DeactivateDidDocumentShape = z.object(DeactivateDidDocumentParams) +export type DeactivateDidDocumentRequestType = z.infer; + +export const DeactivateDidDocumentResponse = z.object({ + didDocument: DidDocumentSchema, + didDocumentMetadata: z.any().describe("DID Document metadata") +}); +export type DeactivateDidDocumentResponseType = z.infer; + + +export const ResolveDidDocumentParams = { + did: DID, +}; + +export const ResolveDidLinkedResourceParams = { + didUrl: DID, +}; + +export const CreateDidLinkedResourceParams = { + did: DID, + name: z.string().describe('Name of DID-Linked Resource'), + type: z.string().describe('Type of DID-Linked Resource. This is NOT the same as the media type, which is calculated automatically ledger-side'), + data: z.string().describe('Encoded string containing the data to be stored in the DID-Linked Resource'), + encoding: z.enum(['base64url', 'base64', 'hex']).describe('Encoding format used to encode the data'), + alsoKnownAs: z + .array( + z.object({ + uri: z.string(), + description: z.string(), + }) + ) + .optional() + .describe('Optional field to assign a set of alternative URIs where the DID-Linked Resource can be fetched from'), + version: z.string().optional().describe('Optional field to assign a human-readable version in the DID-Linked Resource'), + publicKeyHexs: z.array(z.string()).optional().describe('List of key references (publicKeys) in hexadecimal format for signing'), +}; +const CreateDidLinkedResourceShape = z.object(CreateDidLinkedResourceParams) +export type CreateDidLinkedResourceRequestType = z.infer; + +export type CreateDidLinkedResourceResponseType = any; + +// Anonymous Credentials Parameters +export const ResolveSchemaIdParams = { + schemaId: DID_URL.describe( + 'The DID URL of the schema to resolve, e.g., did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009/resources/5acb3d53-ba06-441a-b48b-07d8c2f129f8' + ), +}; + +export const RegisterSchemaParams = { + schema: z.object({ + issuerId: z + .string() + .startsWith('did:cheqd:') + .describe('The DID of the schema issuer, e.g., did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009'), + name: z.string(), + version: z.string(), + attrNames: z.array(z.string()), + }), + options: z.object({ + network: z.enum(['testnet', 'mainnet']), + }), +}; + +export const ListSchemaParams = {}; + +export const CredentialOfferParams = { + issuerDid: z.string().startsWith('did:cheqd:').describe('DID of the Verifiable Credential issuer. This needs to be a `did:cheqd` DID.'), + subjectDid: z.string().describe('DID of the Verifiable Credential holder/subject. This needs to be a `did:key` DID.'), + attributes: z.record(z.unknown()).describe('JSON object containing the attributes to be included in the credential.'), + '@context': z.array(z.string()).optional().describe('Optional properties to be included in the `@context` property of the credential.'), + type: z.array(z.string()).optional().describe('Optional properties to be included in the `type` property of the credential.'), + expirationDate: z.string().datetime().optional().describe('Optional expiration date for the credential.'), + format: z.enum(['jwt', 'jsonld']).optional().default('jwt').describe('Format of the Verifiable Credential.'), + credentialStatus: z.object({ + statusPurpose: z.enum(['revocation', 'suspension']), + statusListName: z.string(), + statusListType: z.enum(['StatusList2021', 'BitstringStatusList']), + statusListIndex: z.number().optional(), + statusListVersion: z.string().datetime().optional(), + statusListRangeStart: z.number().optional(), + statusListRangeEnd: z.number().optional(), + indexNotIn: z.number().optional(), + }).optional().describe('Optional `credentialStatus` properties for VC revocation or suspension.'), + termsOfUse: z.array(z.object({}).passthrough()).optional().describe('Terms of use for the verifiable credential.'), + refreshService: z.array(z.object({}).passthrough()).optional().describe('Refresh services for updating the credential.'), + evidence: z.array(z.object({}).passthrough()).optional().describe('Evidence supporting the credential issuance.'), +}; + +export const ListCredentialParams = {}; + +export const GetCredentialRecordParams = { + credentialId: z.string().uuid(), +}; + +export const AcceptCredentialOfferParams = { + credentialRecordId: z.string().uuid(), +}; + +export const StoreCredentialParams = { + jwt: z.string(), +}; + +// Proof Management Parameters +export const ConnectionlessProofRequestParams = { + requestedAttributes: z + .array( + z.object({ + attribute: z.string(), + restrictions: z.array( + z.object({ + cred_def_id: z.optional( + DID_URL.describe( + 'The DID URL of the credential definition to restrict the proof to, e.g., did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009/resources/5acb3d53-ba06-441a-b48b-07d8c2f129f8' + ) + ), + issuerId: z.optional(DID), + schemaId: z.optional( + DID_URL.describe( + 'The DID URL of the schema to restrict the proof to, e.g., did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009/resources/5acb3d53-ba06-441a-b48b-07d8c2f129f8' + ) + ), + }) + ), + }) + ) + .describe('List of attributes to be revealed in the proof'), + + requestedPredicates: z + .array( + z.object({ + attribute: z.string(), + p_type: z.enum(['>', '<', '>=', '<=']), + p_value: z.number(), + restrictions: z.array( + z.object({ + cred_def_id: z.optional( + DID_URL.describe( + 'The DID URL of the credential definition to restrict the proof to, e.g., did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009/resources/5acb3d53-ba06-441a-b48b-07d8c2f129f8' + ) + ), + issuerId: z.optional(DID), + schemaId: z.optional( + DID_URL.describe( + 'The DID URL of the schema to restrict the proof to, e.g., did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009/resources/5acb3d53-ba06-441a-b48b-07d8c2f129f8' + ) + ), + }) + ), + }) + ) + .describe('List of predicates to be proven without revealing the actual attribute values'), +}; + +export const ConnectionProofRequestParams = { + ...ConnectionlessProofRequestParams, + connectionId: z.string().uuid(), +}; + +export const ListProofParams = {}; + +export const GetProofRecordParams = { + proofRecordId: z.string().uuid(), +}; + +// trust registry +export const ResolveAccreditationParams = { + issuer: z.string().describe('The DID or identifier of the entity that issued the accreditation credential'), + type: z + .array(z.string()) + .describe( + 'Array of credential types that define the nature and purpose of the accreditation (e.g., ["VerifiableCredential", "AccreditationCredential"])' + ), + termsofuse: z.string().describe('Reference to the terms of use type governing the use of this accreditation'), + parentAccreditation: z + .string() + .describe('Reference to a higher-level accreditation that this credential inherits from or is authorized by'), + credentialSchema: z + .string() + .describe( + 'URI pointing to the JSON Schema that defines the structure and validation rules for this accreditation credential' + ), + DNSTrustFrameworkPointers: z + .array(z.string()) + .describe( + 'Array of DNS-based trust framework identifiers that establish the trust context and verification rules for this accreditation' + ), +}; diff --git a/packages/studio/tsconfig.json b/packages/studio/tsconfig.json new file mode 100644 index 0000000..f74c14c --- /dev/null +++ b/packages/studio/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./build", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50303ff..c15f20b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,8 +110,8 @@ importers: packages/remote-server: dependencies: '@cheqd/mcp-toolkit-server': - specifier: 1.4.0-develop.1 - version: 1.4.0-develop.1(encoding@0.1.13) + specifier: 1.4.0 + version: 1.4.0(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) '@modelcontextprotocol/sdk': specifier: ^1.12.1 version: 1.12.1 @@ -159,8 +159,8 @@ importers: packages/server: dependencies: '@cheqd/mcp-toolkit-credo': - specifier: 1.5.0-develop.1 - version: 1.5.0-develop.1(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) + specifier: 1.5.0 + version: 1.5.0(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) '@modelcontextprotocol/sdk': specifier: ^1.12.1 version: 1.12.1 @@ -933,12 +933,12 @@ packages: '@bufbuild/protobuf@2.2.5': resolution: {integrity: sha512-/g5EzJifw5GF8aren8wZ/G5oMuPoGeS6MQD3ca8ddcvdXR5UELUfdTZITCGNhNXynY/AYl3Z4plmxdj/tRl/hQ==} - '@cheqd/mcp-toolkit-credo@1.5.0-develop.1': - resolution: {integrity: sha512-PFH+J0Z7UIkZIQu7oxdlpGC5gWa3nILd4O2SdcsDYHFdoAUR2LyK21BsSz4sxKy9/1z+w+KvvHEENbpR7zwpEg==} + '@cheqd/mcp-toolkit-credo@1.5.0': + resolution: {integrity: sha512-7Buml7TrkQOL1S1h6XefMbSx/dahAtuOSsdyxq0dwdMam5Jdc1uUYhSxhaN57RW2+OluloerpbwpQH8K/as+lw==} engines: {node: '>=20.0.0'} - '@cheqd/mcp-toolkit-server@1.4.0-develop.1': - resolution: {integrity: sha512-e1zn4JYv8JyjrJASA9jW++eOE5O8bKVnBzTicXG/0Cxf7p1OPb5BYoMqnQS7h520zVZL/UtDnxFErldvvB663g==} + '@cheqd/mcp-toolkit-server@1.4.0': + resolution: {integrity: sha512-PaKHYnvKYV0kjXXhi+wdYBns1zSCSY2FRIs7tZ4oi3QSAOSwX4sU5Jmck7MBi28mhFSVCT0ZZ5Nkfev9Up7Dig==} engines: {node: '>=20.0.0'} hasBin: true @@ -8203,7 +8203,7 @@ snapshots: '@bufbuild/protobuf@2.2.5': {} - '@cheqd/mcp-toolkit-credo@1.5.0-develop.1(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3)': + '@cheqd/mcp-toolkit-credo@1.5.0(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3)': dependencies: '@credo-ts/anoncreds': 0.5.15(@hyperledger/anoncreds-shared@0.3.1)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) '@credo-ts/askar': 0.5.15(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) @@ -8229,9 +8229,9 @@ snapshots: - utf-8-validate - web-streams-polyfill - '@cheqd/mcp-toolkit-server@1.4.0-develop.1(encoding@0.1.13)': + '@cheqd/mcp-toolkit-server@1.4.0(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3)': dependencies: - '@cheqd/mcp-toolkit-credo': 1.5.0-develop.1(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) + '@cheqd/mcp-toolkit-credo': 1.5.0(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) '@modelcontextprotocol/sdk': 1.12.1 zod: 3.25.56 transitivePeerDependencies: From e758f49e9c2802a88ce51e70644e4a831a1fdb09 Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Mon, 2 Feb 2026 13:35:12 +0530 Subject: [PATCH 02/10] Update pnpm --- pnpm-lock.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c15f20b..dad280f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -175,6 +175,22 @@ importers: specifier: ^5.8.3 version: 5.8.3 + packages/studio: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.12.1 + version: 1.12.1 + zod: + specifier: ^3.25.56 + version: 3.25.56 + devDependencies: + '@types/node': + specifier: ^24.0.0 + version: 24.0.0 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + packages: '@0no-co/graphql.web@1.1.2': From 66885cb5174e7d159ae4bdbcb1f115bb7c6a9b75 Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Mon, 2 Feb 2026 13:51:49 +0530 Subject: [PATCH 03/10] fix imports --- packages/studio/src/tools/did.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/studio/src/tools/did.ts b/packages/studio/src/tools/did.ts index d085f88..328a3ff 100644 --- a/packages/studio/src/tools/did.ts +++ b/packages/studio/src/tools/did.ts @@ -1,4 +1,3 @@ -import { DidDocument, DidDocumentService } from '@credo-ts/core'; import { StudioAgent } from '../agent.js'; import { CreateDidDocumentParams, From a16c51455b4597acbe547ff74ecef88ef6f3c2ca Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Mon, 2 Feb 2026 14:50:45 +0530 Subject: [PATCH 04/10] Cleanup --- packages/remote-server/src/app.ts | 9 +- packages/studio/src/agent.ts | 44 +++++++-- packages/studio/src/types.ts | 153 +++++++++--------------------- 3 files changed, 90 insertions(+), 116 deletions(-) diff --git a/packages/remote-server/src/app.ts b/packages/remote-server/src/app.ts index 07aef65..da2c66e 100644 --- a/packages/remote-server/src/app.ts +++ b/packages/remote-server/src/app.ts @@ -20,15 +20,20 @@ class App { this.middleware(); this.routes(); const tools = process.env.TOOLS ? normalizeEnvVar(process.env.TOOLS).split(',') : []; + const toolkit = process.env.CHEQD_STUDIO_API_KEY ? 'studio' : 'credo' this.server = new AgentMcpServer({ tools, - credo: { + credo: toolkit === 'credo' ? { port: parseInt(process.env.CREDO_PORT || '3000', 10), domain: normalizeEnvVar(process.env.CREDO_ENDPOINT), name: normalizeEnvVar(process.env.CREDO_NAME), cosmosPayerSeed: normalizeEnvVar(process.env.CREDO_CHEQD_TESTNET_MNEMONIC), trainEndpoint: normalizeEnvVar(process.env.TRAIN_ENDPOINT), - }, + } : undefined, + studio: toolkit === 'studio' ? { + name: normalizeEnvVar(process.env.CHEQD_STUDIO_NAME), + apiKey: normalizeEnvVar(process.env.CHEQD_STUDIO_API_KEY) + } : undefined }); // Initializing the server with tools this.server.setupTools().catch((err) => { diff --git a/packages/studio/src/agent.ts b/packages/studio/src/agent.ts index a89c0e2..e88efa3 100644 --- a/packages/studio/src/agent.ts +++ b/packages/studio/src/agent.ts @@ -1,4 +1,4 @@ -import { IStudioToolKitOptions, CreateDidDocumentResponse, UpdateDidDocumentParams, UpdateDidDocumentResponse, DeactivateDidDocumentParams, CreateDidDocumentRequestType, UpdateDidDocumentRequestType, DeactivateDidDocumentRequestType, CreateDidDocumentResponseType, UpdateDidDocumentResponseType, DeactivateDidDocumentResponseType, CreateDidLinkedResourceRequestType, CreateDidLinkedResourceResponseType } from './types.js'; +import { IStudioToolKitOptions, CreateDidDocumentRequestType, UpdateDidDocumentRequestType, DeactivateDidDocumentRequestType, CreateDidDocumentResponseType, UpdateDidDocumentResponseType, DeactivateDidDocumentResponseType, CreateDidLinkedResourceRequestType, CreateDidLinkedResourceResponseType, IssueCredentialRequest, IssueCredentialResponseType } from './types.js'; import { z } from 'zod' export class StudioAgent { @@ -48,7 +48,7 @@ export class StudioAgent { body: JSON.stringify(body) }); if (!response.ok) { - throw new Error(`Failed to create DID: ${response.statusText}`); + throw new Error(`Failed to update DID: ${response.statusText}`); } return await response.json(); }, @@ -60,7 +60,7 @@ export class StudioAgent { body: JSON.stringify(body) }); if (!response.ok) { - throw new Error(`Failed to create DID: ${response.statusText}`); + throw new Error(`Failed to deactivate DID: ${response.statusText}`); } return await response.json(); }, @@ -68,7 +68,7 @@ export class StudioAgent { getCreatedDids: async(): Promise => { const response = await fetch(`${this.endpoint}/did/list`); if (!response.ok) { - throw new Error(`Failed to resolve DID: ${response.statusText}`); + throw new Error(`Failed to list DID: ${response.statusText}`); } return await response.json(); } @@ -82,7 +82,7 @@ export class StudioAgent { body: JSON.stringify(body) }); if (!response.ok) { - throw new Error(`Failed to create DID: ${response.statusText}`); + throw new Error(`Failed to create DID Linked Resource: ${response.statusText}`); } return await response.json(); }, @@ -110,13 +110,41 @@ export class StudioAgent { public credentials = { - issue: async() => { + issue: async(body: IssueCredentialRequest): Promise => { + const response = await fetch(`${this.endpoint}/credential/issue`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to issue Credential: ${response.statusText}`); + } + return await response.json(); + }, + + verify: async(body) => { + const response = await fetch(`${this.endpoint}/credential/verify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to issue Credential: ${response.statusText}`); + } + return await response.json(); + }, + + getCredentials: async() => { }, - verify: async() => { + getCredential: async() => { - } + }, + + getAllCredentialRecords: async() => { + + }, } public presentations = { diff --git a/packages/studio/src/types.ts b/packages/studio/src/types.ts index f1e5279..8ae2e7c 100644 --- a/packages/studio/src/types.ts +++ b/packages/studio/src/types.ts @@ -168,38 +168,14 @@ export type CreateDidLinkedResourceRequestType = z.infer', '<', '>=', '<=']), - p_value: z.number(), - restrictions: z.array( - z.object({ - cred_def_id: z.optional( - DID_URL.describe( - 'The DID URL of the credential definition to restrict the proof to, e.g., did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009/resources/5acb3d53-ba06-441a-b48b-07d8c2f129f8' - ) - ), - issuerId: z.optional(DID), - schemaId: z.optional( - DID_URL.describe( - 'The DID URL of the schema to restrict the proof to, e.g., did:cheqd:testnet:4769f00d-0af4-472b-aab7-019abbbb8009/resources/5acb3d53-ba06-441a-b48b-07d8c2f129f8' - ) - ), - }) - ), - }) - ) - .describe('List of predicates to be proven without revealing the actual attribute values'), -}; - -export const ConnectionProofRequestParams = { - ...ConnectionlessProofRequestParams, - connectionId: z.string().uuid(), -}; + termsOfUse: z.array(z.record(z.unknown())).optional().describe('Terms of use can be utilized by an issuer or a holder to communicate the terms under which a verifiable credential was issued.'), + refreshService: z.array(z.record(z.unknown())).optional().describe('RefreshService property MUST be one or more refresh services that provides enough information to the recipient\'s software.'), + evidence: z.array(z.record(z.unknown())).optional().describe('Evidence property MUST be one or more evidence schemes providing enough information for a verifier.'), + connector: z.enum(['verida', 'resource']).optional(), +} +const IssueCredentialShape = z.object(IssueCredentialParams) +export type IssueCredentialRequest = z.infer + +export const VerifiableCredential = z.object({ + '@context': z.union([z.string(), z.array(z.string())]).describe('JSON-LD context'), + id: z.string().optional().describe('Credential identifier'), + type: z.array(z.string()).describe('Credential types'), + issuer: z.union([z.string(), z.object({ id: z.string() })]).describe('Credential issuer'), + issuanceDate: z.string().datetime().describe('Issuance date'), + expirationDate: z.string().datetime().optional().describe('Expiration date'), + credentialSubject: z.record(z.unknown()).describe('Credential subject claims'), + proof: z.record(z.unknown()).optional().describe('Cryptographic proof'), + credentialStatus: z.record(z.unknown()).optional().describe('Credential status information'), +}) -export const ListProofParams = {}; +export const IssueCredentialResponse = z.object({ + issuedCredentialId: z.string().describe('Unique identifier for the issued credential'), + providerId: z.string().describe('Provider identifier'), + providerCredentialId: z.string().optional().describe('Provider-specific credential ID'), + issuerId: z.string().describe('DID or identifier of the credential issuer'), + subjectId: z.string().describe('DID or identifier of the credential subject'), + format: z.string().describe('Credential format (e.g., jwt_vc, jsonld)'), + category: z.enum(['credential', 'accreditation']).optional().describe('Credential category'), + type: z.array(z.string()).describe('Array of credential types'), + status: z.enum(['active', 'revoked', 'suspended', 'expired']).describe('Current status of the credential'), + statusUpdatedAt: z.string().datetime().optional().describe('Timestamp when status was last updated'), + issuedAt: z.string().datetime().describe('Timestamp when credential was issued'), + expiresAt: z.string().datetime().optional().describe('Timestamp when credential expires'), + credentialStatus: z.record(z.unknown()).optional().describe('Credential status configuration'), + statusRegistryId: z.string().optional().describe('UUID of the Status Registry'), + statusIndex: z.number().optional().describe('Allocated Index of the Status Registry'), + retryCount: z.number().optional().describe('Retry Count in case of failures'), + lastError: z.string().optional().describe('Last error message in case of failure'), + providerMetadata: z.record(z.unknown()).optional().describe('Provider-specific metadata'), + credential: VerifiableCredential.describe('The issued Verifiable Credential'), + createdAt: z.string().datetime().describe('Timestamp when record was created'), + updatedAt: z.string().datetime().describe('Timestamp when record was last updated'), +}) -export const GetProofRecordParams = { - proofRecordId: z.string().uuid(), -}; +export type IssueCredentialResponseType = z.infer // trust registry export const ResolveAccreditationParams = { From 40dfcba2ff8d8ea77adc6d7ce1f047c7e28b710c Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Mon, 2 Feb 2026 15:07:03 +0530 Subject: [PATCH 05/10] Update server --- packages/remote-server/src/types/index.ts | 4 +++ packages/server/package.json | 1 + packages/server/src/server.ts | 38 +++++++++++++++++++++- packages/server/src/types/environment.d.ts | 2 ++ packages/server/src/types/index.ts | 4 +++ pnpm-lock.yaml | 16 +++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/remote-server/src/types/index.ts b/packages/remote-server/src/types/index.ts index a68be76..9669b47 100644 --- a/packages/remote-server/src/types/index.ts +++ b/packages/remote-server/src/types/index.ts @@ -7,4 +7,8 @@ export interface IAgentMCPServerOptions { domain?: string; cosmosPayerSeed?: string; }; + studio?: { + name: string; + apiKey: string; + } } diff --git a/packages/server/package.json b/packages/server/package.json index 5edea9a..82efc32 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -31,6 +31,7 @@ ], "dependencies": { "@cheqd/mcp-toolkit-credo": "1.5.1-develop.1", + "@cheqd/mcp-toolkit-studio": "file:../studio", "@modelcontextprotocol/sdk": "^1.20.2", "zod": "^3.25.56" }, diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 14dc0ff..57f40e5 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -2,6 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { CredoToolKit } from '@cheqd/mcp-toolkit-credo'; +import { StudioToolKit } from '@cheqd/mcp-toolkit-studio'; import { ToolDefinition } from '@cheqd/mcp-toolkit-credo/build/types.js'; import { IAgentMCPServerOptions } from './types/index.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; @@ -20,6 +21,7 @@ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8')); export class AgentMcpServer extends McpServer { private transport: StdioServerTransport | SSEServerTransport | StreamableHTTPServerTransport | null = null; private credoToolkit: CredoToolKit | null = null; + private studioToolkit: StudioToolKit | null = null; private options: IAgentMCPServerOptions; /** @@ -125,8 +127,16 @@ export class AgentMcpServer extends McpServer { this.credoToolkit.registerResources(this); this.credoToolkit.registerPrompts(this); } + } else { + await this.setupStudioTools(tools); + if (this.credoToolkit) { + this.credoToolkit.registerResources(this); + this.credoToolkit.registerPrompts(this); + } } + + // Register all tools with the server for (const tool of tools) { this.tool(tool.name, tool.description, tool.schema, tool.handler); @@ -159,6 +169,30 @@ export class AgentMcpServer extends McpServer { } } + /** + * Set up Credo-specific tools + */ + private async setupStudioTools(tools: ToolDefinition[]): Promise { + // Validate required env variables + if (!this.options.studio?.apiKey) { + throw new Error( + 'Missing required environment variables for Credo tools. Please set: CREDO_CHEQD_TESTNET_MNEMONIC' + ); + } + try { + this.studioToolkit = new StudioToolKit({ + name: this.options.studio.name, + apiKey: this.options.studio.apiKey, + }); + await this.studioToolkit.init(); + const studioTools = await this.studioToolkit.getTools(); + + tools.push(...(studioTools as ToolDefinition[])); + } catch (err) { + throw new Error(`Studio initialization failed: ${err instanceof Error ? err.message : String(err)}`); + } + } + /* * Start the server and connect to the specified transport */ @@ -183,11 +217,13 @@ export class AgentMcpServer extends McpServer { async cleanup(): Promise { console.error('Shutdown signal received, cleaning up server...'); try { - const { credoToolkit, transport } = this; + const { credoToolkit, studioToolkit, transport } = this; // Shutdown the agent if available if (credoToolkit) { await credoToolkit.shutdown(); + } else if (studioToolkit) { + await studioToolkit.shutdown(); } // Close transport if it's a StdioServerTransport diff --git a/packages/server/src/types/environment.d.ts b/packages/server/src/types/environment.d.ts index 2c34559..9adc4a5 100644 --- a/packages/server/src/types/environment.d.ts +++ b/packages/server/src/types/environment.d.ts @@ -7,6 +7,8 @@ declare global { CREDO_NAME: string | 'credo-agent'; CREDO_ENDPOINT?: string; TRAIN_ENDPOINT?: string | 'https://dev-train.trust-scheme.de/tcr/v1/'; + CHEQD_STUDIO_API_ENDPOINT?: string | 'https://studio-api-staging.cheqd.net'; + CHEQD_STUDIO_API_KEY?: string; } } } diff --git a/packages/server/src/types/index.ts b/packages/server/src/types/index.ts index f6fefba..f8ed771 100644 --- a/packages/server/src/types/index.ts +++ b/packages/server/src/types/index.ts @@ -7,4 +7,8 @@ export interface IAgentMCPServerOptions { cosmosPayerSeed?: string; trainEndpoint?: string; }; + studio?: { + name: string; + apiKey: string; + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c63d942..819e5c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -161,6 +161,9 @@ importers: '@cheqd/mcp-toolkit-credo': specifier: 1.5.1-develop.1 version: 1.5.1-develop.1(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(hono@4.11.7)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) + '@cheqd/mcp-toolkit-studio': + specifier: file:../studio + version: file:packages/studio(hono@4.11.7) '@modelcontextprotocol/sdk': specifier: ^1.20.2 version: 1.25.3(hono@4.11.7)(zod@3.25.56) @@ -958,6 +961,10 @@ packages: engines: {node: '>=20.0.0'} hasBin: true + '@cheqd/mcp-toolkit-studio@file:packages/studio': + resolution: {directory: packages/studio, type: directory} + engines: {node: '>=20.0.0'} + '@cheqd/sdk@5.2.2': resolution: {integrity: sha512-nVqV/Sx7IJTsT3lUXGo4Jg/ei3OjmNDAOTcrfSpe6YYXuJDQ2gMKEi2SiHxX/GByKGssOLXXvZSKa91K9HlXbQ==} engines: {node: '>=20.0.0'} @@ -8343,6 +8350,15 @@ snapshots: - utf-8-validate - web-streams-polyfill + '@cheqd/mcp-toolkit-studio@file:packages/studio(hono@4.11.7)': + dependencies: + '@modelcontextprotocol/sdk': 1.25.3(hono@4.11.7)(zod@3.25.56) + zod: 3.25.56 + transitivePeerDependencies: + - '@cfworker/json-schema' + - hono + - supports-color + '@cheqd/sdk@5.2.2': dependencies: '@cheqd/ts-proto': 4.0.2 From 3eef12ddf4ef3ff1d356b344a8fe3e9ea43810c2 Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Mon, 2 Feb 2026 16:09:00 +0530 Subject: [PATCH 06/10] switch to workspace imports --- packages/remote-server/package.json | 2 +- packages/server/package.json | 2 +- packages/studio/src/index.ts | 4 +-- packages/studio/src/types.ts | 1 + pnpm-lock.yaml | 46 +++-------------------------- 5 files changed, 9 insertions(+), 46 deletions(-) diff --git a/packages/remote-server/package.json b/packages/remote-server/package.json index 1dad06f..d739330 100644 --- a/packages/remote-server/package.json +++ b/packages/remote-server/package.json @@ -23,7 +23,7 @@ "mcp" ], "dependencies": { - "@cheqd/mcp-toolkit-server": "1.4.1-develop.1", + "@cheqd/mcp-toolkit-server": "workspace:*", "@modelcontextprotocol/sdk": "^1.20.2", "zod": "^3.25.56", "cookie-parser": "^1.4.7", diff --git a/packages/server/package.json b/packages/server/package.json index 82efc32..b46365f 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -31,7 +31,7 @@ ], "dependencies": { "@cheqd/mcp-toolkit-credo": "1.5.1-develop.1", - "@cheqd/mcp-toolkit-studio": "file:../studio", + "@cheqd/mcp-toolkit-studio": "workspace:*", "@modelcontextprotocol/sdk": "^1.20.2", "zod": "^3.25.56" }, diff --git a/packages/studio/src/index.ts b/packages/studio/src/index.ts index 8cf022e..4c4eefe 100644 --- a/packages/studio/src/index.ts +++ b/packages/studio/src/index.ts @@ -15,8 +15,8 @@ export class StudioToolKit { * @param {IStudioToolKitOptions} options - Configuration options for the toolkit * @param {string} options.name - Name of the agent */ - constructor({ name }: IStudioToolKitOptions) { - this.studio = new StudioAgent({ name }) + constructor({ name, apiKey }: IStudioToolKitOptions) { + this.studio = new StudioAgent({ name, apiKey }) } /// Initializes the Studio agent and prepares it for use. diff --git a/packages/studio/src/types.ts b/packages/studio/src/types.ts index 8ae2e7c..e2cc458 100644 --- a/packages/studio/src/types.ts +++ b/packages/studio/src/types.ts @@ -17,6 +17,7 @@ export interface ToolDefinition { */ export interface IStudioToolKitOptions { name: string; + apiKey: string; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 819e5c0..07eab3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,8 +110,8 @@ importers: packages/remote-server: dependencies: '@cheqd/mcp-toolkit-server': - specifier: 1.4.1-develop.1 - version: 1.4.1-develop.1(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(hono@4.11.7)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) + specifier: workspace:* + version: link:../server '@modelcontextprotocol/sdk': specifier: ^1.20.2 version: 1.25.3(hono@4.11.7)(zod@3.25.56) @@ -162,8 +162,8 @@ importers: specifier: 1.5.1-develop.1 version: 1.5.1-develop.1(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(hono@4.11.7)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) '@cheqd/mcp-toolkit-studio': - specifier: file:../studio - version: file:packages/studio(hono@4.11.7) + specifier: workspace:* + version: link:../studio '@modelcontextprotocol/sdk': specifier: ^1.20.2 version: 1.25.3(hono@4.11.7)(zod@3.25.56) @@ -956,15 +956,6 @@ packages: resolution: {integrity: sha512-dgu6mk7KhLZoL6L4zpZTfdzddaH1970nb4bZAII45Az5Hj75MxFLopNOpuTIFSC1WxBluykwHfhtTAfU9in7/g==} engines: {node: '>=20.0.0'} - '@cheqd/mcp-toolkit-server@1.4.1-develop.1': - resolution: {integrity: sha512-MHwr4igSM7Z2lhxDNn6ZJ7lkjMQ0kAiLmaL6loyVqJM4zhDzVtjrJ4qRQYE+2+q8MItqLEQ5/WcM+1dJxKyMrg==} - engines: {node: '>=20.0.0'} - hasBin: true - - '@cheqd/mcp-toolkit-studio@file:packages/studio': - resolution: {directory: packages/studio, type: directory} - engines: {node: '>=20.0.0'} - '@cheqd/sdk@5.2.2': resolution: {integrity: sha512-nVqV/Sx7IJTsT3lUXGo4Jg/ei3OjmNDAOTcrfSpe6YYXuJDQ2gMKEi2SiHxX/GByKGssOLXXvZSKa91K9HlXbQ==} engines: {node: '>=20.0.0'} @@ -8330,35 +8321,6 @@ snapshots: - utf-8-validate - web-streams-polyfill - '@cheqd/mcp-toolkit-server@1.4.1-develop.1(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(hono@4.11.7)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3)': - dependencies: - '@cheqd/mcp-toolkit-credo': 1.5.1-develop.1(@hyperledger/aries-askar-shared@0.2.3)(encoding@0.1.13)(expo@52.0.46(@babel/core@7.27.4)(@babel/preset-env@7.27.2(@babel/core@7.27.4))(encoding@0.1.13)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(react@19.1.0))(hono@4.11.7)(react-native@0.79.3(@babel/core@7.27.4)(react@19.1.0))(web-streams-polyfill@3.3.3) - '@modelcontextprotocol/sdk': 1.25.3(hono@4.11.7)(zod@3.25.56) - zod: 3.25.56 - transitivePeerDependencies: - - '@animo-id/expo-secure-environment' - - '@cfworker/json-schema' - - '@hyperledger/aries-askar-shared' - - bufferutil - - debug - - domexception - - encoding - - expo - - hono - - react-native - - supports-color - - utf-8-validate - - web-streams-polyfill - - '@cheqd/mcp-toolkit-studio@file:packages/studio(hono@4.11.7)': - dependencies: - '@modelcontextprotocol/sdk': 1.25.3(hono@4.11.7)(zod@3.25.56) - zod: 3.25.56 - transitivePeerDependencies: - - '@cfworker/json-schema' - - hono - - supports-color - '@cheqd/sdk@5.2.2': dependencies: '@cheqd/ts-proto': 4.0.2 From d5c0bb29f82a0f53b6bc2f8f562dd1235d6a04d5 Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Mon, 2 Feb 2026 17:32:22 +0530 Subject: [PATCH 07/10] Add credential tools for studio --- packages/studio/src/agent.ts | 75 ++++++++-- packages/studio/src/index.ts | 10 ++ packages/studio/src/tools/credential.ts | 172 +++++++++++++++++++++++ packages/studio/src/tools/index.ts | 2 + packages/studio/src/tools/status-list.ts | 68 +++++++++ packages/studio/src/types.ts | 75 ++++++++++ 6 files changed, 389 insertions(+), 13 deletions(-) create mode 100644 packages/studio/src/tools/credential.ts create mode 100644 packages/studio/src/tools/status-list.ts diff --git a/packages/studio/src/agent.ts b/packages/studio/src/agent.ts index e88efa3..cd5cd39 100644 --- a/packages/studio/src/agent.ts +++ b/packages/studio/src/agent.ts @@ -1,4 +1,4 @@ -import { IStudioToolKitOptions, CreateDidDocumentRequestType, UpdateDidDocumentRequestType, DeactivateDidDocumentRequestType, CreateDidDocumentResponseType, UpdateDidDocumentResponseType, DeactivateDidDocumentResponseType, CreateDidLinkedResourceRequestType, CreateDidLinkedResourceResponseType, IssueCredentialRequest, IssueCredentialResponseType } from './types.js'; +import { IStudioToolKitOptions, CreateDidDocumentRequestType, UpdateDidDocumentRequestType, DeactivateDidDocumentRequestType, CreateDidDocumentResponseType, UpdateDidDocumentResponseType, DeactivateDidDocumentResponseType, CreateDidLinkedResourceRequestType, CreateDidLinkedResourceResponseType, IssueCredentialRequest, IssueCredentialResponseType, VerifyCredentialRequestType, CredentialStatusListCreateRequest, CredentialStatusListUpdateRequest } from './types.js'; import { z } from 'zod' export class StudioAgent { @@ -65,8 +65,8 @@ export class StudioAgent { return await response.json(); }, - getCreatedDids: async(): Promise => { - const response = await fetch(`${this.endpoint}/did/list`); + getCreatedDids: async(params?: string): Promise => { + const response = await fetch(`${this.endpoint}/did/list${params}`); if (!response.ok) { throw new Error(`Failed to list DID: ${response.statusText}`); } @@ -98,13 +98,29 @@ export class StudioAgent { public statusList = { - create: async() => { - + create: async(body: CredentialStatusListCreateRequest) => { + const response = await fetch(`${this.endpoint}/credential-status/create/unencrypted`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to create DID Linked Resource: ${response.statusText}`); + } + return await response.json(); }, - update: async() => { - - } + update: async(body: CredentialStatusListUpdateRequest) => { + const response = await fetch(`${this.endpoint}/credential-status/update/unencrypted`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to create DID Linked Resource: ${response.statusText}`); + } + return await response.json(); + }, } @@ -122,7 +138,7 @@ export class StudioAgent { return await response.json(); }, - verify: async(body) => { + verify: async(body: VerifyCredentialRequestType) => { const response = await fetch(`${this.endpoint}/credential/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -134,16 +150,49 @@ export class StudioAgent { return await response.json(); }, - getCredentials: async() => { - + getCredentialExchangeRecords: async(params?: string) => { + const response = await fetch(`${this.endpoint}/did/list?${params}`); + if (!response.ok) { + throw new Error(`Failed to list DID: ${response.statusText}`); + } + return await response.json(); }, - getCredential: async() => { + revoke: async(body) => { + const response = await fetch(`${this.endpoint}/credential/revoke`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to issue Credential: ${response.statusText}`); + } + return await response.json(); }, - getAllCredentialRecords: async() => { + reinstate: async(body) => { + const response = await fetch(`${this.endpoint}/credential/reinstate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to issue Credential: ${response.statusText}`); + } + return await response.json(); + }, + suspend: async(body) => { + const response = await fetch(`${this.endpoint}/credential/suspend`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Failed to issue Credential: ${response.statusText}`); + } + return await response.json(); }, } diff --git a/packages/studio/src/index.ts b/packages/studio/src/index.ts index 4c4eefe..8258604 100644 --- a/packages/studio/src/index.ts +++ b/packages/studio/src/index.ts @@ -1,7 +1,9 @@ import { StudioAgent } from './agent.js'; import { + CredentialToolHandler, DidToolHandler, } from './tools/index.js'; +import { CredentialStatusListToolHandler } from './tools/status-list.js'; import { IStudioToolKitOptions } from './types.js'; /** @@ -49,6 +51,14 @@ export class StudioToolKit { new DidToolHandler(this.studio).listDidTool(), new DidToolHandler(this.studio).createDIDLinkedResourceTool(), new DidToolHandler(this.studio).resolveDIDLinkedResourceTool(), + new CredentialToolHandler(this.studio).IssueCredentialTool(), + new CredentialToolHandler(this.studio).verifyCredentialTool(), + new CredentialToolHandler(this.studio).listCredentialExchangeRecordsTool(), + new CredentialToolHandler(this.studio).revokeCredentialTool(), + new CredentialToolHandler(this.studio).suspendCredentialTool(), + new CredentialToolHandler(this.studio).reinstateCredentialTool(), + new CredentialStatusListToolHandler(this.studio).StatusListCreateTool(), + new CredentialStatusListToolHandler(this.studio).StatusListUpdateTool(), ]; } /** diff --git a/packages/studio/src/tools/credential.ts b/packages/studio/src/tools/credential.ts new file mode 100644 index 0000000..83b23b1 --- /dev/null +++ b/packages/studio/src/tools/credential.ts @@ -0,0 +1,172 @@ +import { StudioAgent } from '../agent.js'; +import { + IssueCredentialParams, + ListCredentialExchangeRecordsParams, + ToolDefinition, + VerifyCredentialParams, +} from '../types.js'; + +/** + * Handler class for managing credentials in the Credo agent. + * Provides tools for creating credential offers, managing credentials, and handling credential records. + */ +export class CredentialToolHandler { + studio: StudioAgent; + + constructor(studio: StudioAgent) { + this.studio = studio; + } + + /** + * Creates a connectionless credential offer that can be accepted by any holder. + * Generates a QR code for the offer that can be scanned to initiate credential issuance. + */ + IssueCredentialTool(): ToolDefinition { + return { + name: 'issue-credential', + description: + 'Issues a credential to the subject did, the credential can also be sent to a wallet by using external connectors.', + schema: IssueCredentialParams, + handler: async (body) => { + const result = await this.studio.credentials.issue(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify('result') + }, + ], + }; + }, + }; + } + + /** + * Lists all credential exchange records in the agent's wallet. + * Provides a complete overview of all credential exchange records. + */ + listCredentialExchangeRecordsTool(): ToolDefinition { + return { + name: 'list-credential-exchange-records', + description: + "Retrieves all credential exchange records from the agent's wallet, providing a comprehensive list of all credential exchanges with their states and associated metadata.", + schema: ListCredentialExchangeRecordsParams, + handler: async () => { + const credentials = await this.studio.credentials.getCredentialExchangeRecords(); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(credentials), + }, + ], + }; + }, + }; + } + + + /** + * Verifies a credential to ensure its authenticity and validity. + * Checks the credential signature and expiration status. + */ + verifyCredentialTool(): ToolDefinition { + return { + name: 'verify-credential', + description: + "Verifies a credential's authenticity and validity by checking its signature, expiration status, and issuer information.", + schema: VerifyCredentialParams, + handler: async (body) => { + const result = await this.studio.credentials.verify(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; + } + + // +/** + * Revokes a previously issued credential. + * The credential can no longer be used or verified after revocation. + */ +revokeCredentialTool(): ToolDefinition { + return { + name: 'revoke-credential', + description: + 'Revokes a previously issued credential, making it invalid for future verification.', + schema: VerifyCredentialParams, + handler: async (body) => { + const result = await this.studio.credentials.revoke(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; +} + +/** + * Suspends a credential temporarily. + * The credential can be reinstated later if needed. + */ +suspendCredentialTool(): ToolDefinition { + return { + name: 'suspend-credential', + description: + 'Temporarily suspends a credential, preventing verification until it is reinstated.', + schema: VerifyCredentialParams, + handler: async (body) => { + const result = await this.studio.credentials.suspend(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; +} + +/** + * Reinstates a previously suspended credential. + * The credential becomes valid for verification again. + */ +reinstateCredentialTool(): ToolDefinition { + return { + name: 'reinstate-credential', + description: + 'Reinstates a suspended credential, making it valid for verification again.', + schema: VerifyCredentialParams, + handler: async (body) => { + const result = await this.studio.credentials.reinstate(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; + }, + }; +} +} + diff --git a/packages/studio/src/tools/index.ts b/packages/studio/src/tools/index.ts index 4ec38b3..2ac9374 100644 --- a/packages/studio/src/tools/index.ts +++ b/packages/studio/src/tools/index.ts @@ -1 +1,3 @@ export * from './did.js'; +export * from './credential.js' +export * from './status-list.js' \ No newline at end of file diff --git a/packages/studio/src/tools/status-list.ts b/packages/studio/src/tools/status-list.ts new file mode 100644 index 0000000..345a72f --- /dev/null +++ b/packages/studio/src/tools/status-list.ts @@ -0,0 +1,68 @@ +import { StudioAgent } from '../agent.js'; +import { + CredentialStatusListCreateParams, + CredentialStatusListUpdateParams, + IssueCredentialParams, + ToolDefinition, +} from '../types.js'; + +/** + * Handler class for managing credentials in the Credo agent. + * Provides tools for creating credential offers, managing credentials, and handling credential records. + */ +export class CredentialStatusListToolHandler { + studio: StudioAgent; + + constructor(studio: StudioAgent) { + this.studio = studio; + } + + /** + * Creates a new status list credential for managing credential revocation. + */ + StatusListCreateTool(): ToolDefinition { + return { + name: 'statuslist-create', + description: + 'Creates a new status list credential that can be used to manage the revocation status of issued credentials.', + schema: CredentialStatusListCreateParams, + handler: async (body) => { + const result = await this.studio.statusList.create(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result) + }, + ], + }; + }, + }; + } + + /** + * Updates an existing status list credential to revoke or reinstate credentials. + */ + StatusListUpdateTool(): ToolDefinition { + return { + name: 'statuslist-update', + description: + 'Updates an existing status list credential to change the revocation status of issued credentials.', + schema: CredentialStatusListUpdateParams, + handler: async (body) => { + const result = await this.studio.statusList.update(body); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result) + }, + ], + }; + }, + }; + } +} + diff --git a/packages/studio/src/types.ts b/packages/studio/src/types.ts index e2cc458..5b240e6 100644 --- a/packages/studio/src/types.ts +++ b/packages/studio/src/types.ts @@ -256,3 +256,78 @@ export const ResolveAccreditationParams = { 'Array of DNS-based trust framework identifiers that establish the trust context and verification rules for this accreditation' ), }; + +export const ListCredentialExchangeRecordsParams = { + page: z.number().optional().default(1).describe('Page number for pagination'), + limit: z.number().optional().default(10).describe('Number of items per page'), + providerId: z.string().optional().describe('Filter credentials by provider ID (e.g., "studio", "dock")'), + issuerId: z.string().optional().describe('Filter credentials by issuer DID or ID'), + subjectId: z.string().optional().describe('Filter credentials by subject DID or ID'), + status: z.enum(['issued', 'suspended', 'revoked', 'offered', 'rejected', 'unknown', 'valid']).optional().describe('Filter credentials by status'), + format: z.enum(['jwt', 'jsonld', 'sd-jwt-vc', 'anoncreds']).optional().describe('Filter credentials by format'), + category: z.enum(['credential', 'accreditation']).optional().describe('Filter credentials by category'), + createdAt: z.string().datetime().optional().describe('Filter credentials created before or on this date'), + credentialType: z.string().optional().describe('Filter credentials by type (e.g., "VerifiableCredential", "UniversityDegreeCredential")'), + statusRegistryId: z.string().optional().describe('Filter issued credentials using status registry ID'), +} +const ListCredentialExchangeRecordsShape = z.object(ListCredentialExchangeRecordsParams) +export type ListCredentialExchangeRecordsRequest = z.infer + +export const ListCredentialResult = z.object({ + total: z.number().describe('Total number of credentials'), + credentials: z.array(IssueCredentialResponse).describe('Array of issued credentials'), +}); + +export type ListCredentialResultType = z.infer; + +export const VerifyCredentialParams = { + credential: z.union([z.string(), VerifiableCredential]).describe('Verifiable Credential to be verified as a VC-JWT string or a JSON object.'), + policies: z.object({ + issuanceDate: z.boolean().optional().default(true).describe('Policy to skip the `issuanceDate` (`nbf`) timestamp check when set to `false`.'), + expirationDate: z.boolean().optional().default(true).describe('Policy to skip the `expirationDate` (`exp`) timestamp check when set to `false`.'), + audience: z.boolean().optional().default(false).describe('Policy to skip the audience check when set to `false`.'), + checkExternalProvider: z.boolean().optional().default(false).describe('Policy to also check other providers when set to `true`.'), + }).optional().describe('Custom verification policies to execute when verifying credential.'), +} + +const VerifyCredentialShape = z.object(VerifyCredentialParams) +export type VerifyCredentialRequestType = z.infer + +export const VerifiableCredentialParams = { + credential: z.union([z.string(), VerifiableCredential]).describe('Verifiable Credential to be verified as a VC-JWT string or a JSON object.'), +} + +const VerifiableCredentialShape = z.object(VerifiableCredentialParams) +export type VerifiableCredentialRequest = z.infer + +export const CredentialStatusListCreateParams = { + did: DID, + statusListName: z.string().describe('The name of the StatusList2021 or BitstringStatusList DID-Linked Resource to be created'), + length: z.number().int().positive().default(131072).describe('The length of the status list to be created. Default and minimum is 131072 (16kb)'), + encoding: z.enum(['base64url', 'hex']).default('base64url').describe('The encoding format of the StatusList DID-Linked Resource'), + statusListVersion: z.string().optional().describe('Optional human-readable version in the StatusList DID-Linked Resource'), + statusSize: z.number().int().positive().optional().describe('Only for BitstringStatusList: bits per credential for multiple statuses'), + credentialCategory: z.enum(['credential', 'accreditation']).optional().describe('Category of credentials this status list is for'), + statusMessages: z.array(z.object({ + status: z.string(), + message: z.string(), + })).optional().describe('Only for BitstringStatusList: Message explaining each bit'), + ttl: z.number().int().min(1000).optional().describe('Only for BitstringStatusList: Time to Live in Milliseconds'), + alsoKnownAs: z.array(z.object({ + uri: z.string(), + description: z.string(), + })).optional().describe('Optional alternative URIs for the status list'), +} + +const CredentialStatusListCreateShape = z.object(CredentialStatusListCreateParams) +export type CredentialStatusListCreateRequest = z.infer + +export const CredentialStatusListUpdateParams = { + did: DID, + statusListName: z.string().describe('The name of the StatusList2021 DID-Linked Resource to be updated'), + indices: z.array(z.number().int().nonnegative()).describe('List of credential status indices to be updated. The indices must be in the range of the status list.'), + statusListVersion: z.string().optional().describe('Optional field to assign a human-readable version in the StatusList2021 DID-Linked Resource'), +} + +const CredentialStatusListUpdateShape = z.object(CredentialStatusListUpdateParams) +export type CredentialStatusListUpdateRequest = z.infer \ No newline at end of file From 490d621ca2c07fe1edc37e3c888bbeaaad3f8798 Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Mon, 2 Feb 2026 19:24:30 +0530 Subject: [PATCH 08/10] Pass api endpoint in config --- packages/remote-server/src/app.ts | 3 +- packages/server/src/index.ts | 4 + packages/server/src/server.ts | 7 +- packages/server/src/types/environment.d.ts | 2 +- packages/server/src/types/index.ts | 3 +- .../tests/{ => credo}/01-server.spec.ts | 0 .../02-did.spec copy.ts} | 0 packages/server/tests/credo/02-did.spec.ts | 120 ++++++++++++++++ .../tests/{ => credo}/03-anoncreds.spec.ts | 0 .../tests/{ => credo}/04-connection.spec.ts | 0 .../tests/{ => credo}/05-credential.spec.ts | 0 .../server/tests/{ => credo}/06-proof.spec.ts | 0 packages/server/tests/{ => credo}/setup.ts | 2 +- packages/server/tests/{ => credo}/state.ts | 0 packages/server/tests/{ => credo}/utils.ts | 2 +- .../server/tests/studio/01-server.spec.ts | 43 ++++++ packages/server/tests/studio/setup.ts | 132 ++++++++++++++++++ packages/studio/src/agent.ts | 52 +++---- packages/studio/src/index.ts | 4 +- packages/studio/src/types.ts | 3 +- playwright.global-setup.ts | 2 +- playwright.global-teardown.ts | 2 +- 22 files changed, 344 insertions(+), 37 deletions(-) rename packages/server/tests/{ => credo}/01-server.spec.ts (100%) rename packages/server/tests/{02-did.spec.ts => credo/02-did.spec copy.ts} (100%) create mode 100644 packages/server/tests/credo/02-did.spec.ts rename packages/server/tests/{ => credo}/03-anoncreds.spec.ts (100%) rename packages/server/tests/{ => credo}/04-connection.spec.ts (100%) rename packages/server/tests/{ => credo}/05-credential.spec.ts (100%) rename packages/server/tests/{ => credo}/06-proof.spec.ts (100%) rename packages/server/tests/{ => credo}/setup.ts (99%) rename packages/server/tests/{ => credo}/state.ts (100%) rename packages/server/tests/{ => credo}/utils.ts (98%) create mode 100644 packages/server/tests/studio/01-server.spec.ts create mode 100644 packages/server/tests/studio/setup.ts diff --git a/packages/remote-server/src/app.ts b/packages/remote-server/src/app.ts index da2c66e..82571a5 100644 --- a/packages/remote-server/src/app.ts +++ b/packages/remote-server/src/app.ts @@ -32,7 +32,8 @@ class App { } : undefined, studio: toolkit === 'studio' ? { name: normalizeEnvVar(process.env.CHEQD_STUDIO_NAME), - apiKey: normalizeEnvVar(process.env.CHEQD_STUDIO_API_KEY) + apiKey: normalizeEnvVar(process.env.CHEQD_STUDIO_API_KEY), + apiEndpoint: normalizeEnvVar(process.env.CHEQD_STUDIO_API_ENDPOINT) } : undefined }); // Initializing the server with tools diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 76cd1e1..fe24ca8 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -17,6 +17,10 @@ async function initializeServer() { cosmosPayerSeed: normalizeEnvVar(process.env.CREDO_CHEQD_TESTNET_MNEMONIC), trainEndpoint: normalizeEnvVar(process.env.TRAIN_ENDPOINT), }, + studio: process.env.CHEQD_STUDIO_API_KEY && process.env.CHEQD_STUDIO_API_ENDPOINT ? { + apiKey: normalizeEnvVar(process.env.CHEQD_STUDIO_API_KEY), + apiEndpoint: normalizeEnvVar(process.env.CHEQD_STUDIO_API_ENDPOINT) + } : undefined }); try { diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 57f40e5..7690f49 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -174,15 +174,16 @@ export class AgentMcpServer extends McpServer { */ private async setupStudioTools(tools: ToolDefinition[]): Promise { // Validate required env variables - if (!this.options.studio?.apiKey) { + if (!this.options.studio?.apiKey || !this.options.studio.apiEndpoint) { throw new Error( - 'Missing required environment variables for Credo tools. Please set: CREDO_CHEQD_TESTNET_MNEMONIC' + 'Missing required environment variables for Studio tools. Please set: CHEQD_STUDIO_API_KEY' ); } try { this.studioToolkit = new StudioToolKit({ - name: this.options.studio.name, + name: this.options.studio.name || 'default', apiKey: this.options.studio.apiKey, + apiEndpoint: this.options.studio.apiEndpoint }); await this.studioToolkit.init(); const studioTools = await this.studioToolkit.getTools(); diff --git a/packages/server/src/types/environment.d.ts b/packages/server/src/types/environment.d.ts index 9adc4a5..a471c64 100644 --- a/packages/server/src/types/environment.d.ts +++ b/packages/server/src/types/environment.d.ts @@ -7,7 +7,7 @@ declare global { CREDO_NAME: string | 'credo-agent'; CREDO_ENDPOINT?: string; TRAIN_ENDPOINT?: string | 'https://dev-train.trust-scheme.de/tcr/v1/'; - CHEQD_STUDIO_API_ENDPOINT?: string | 'https://studio-api-staging.cheqd.net'; + CHEQD_STUDIO_API_ENDPOINT: string | 'https://studio-api-staging.cheqd.net'; CHEQD_STUDIO_API_KEY?: string; } } diff --git a/packages/server/src/types/index.ts b/packages/server/src/types/index.ts index f8ed771..40354fb 100644 --- a/packages/server/src/types/index.ts +++ b/packages/server/src/types/index.ts @@ -8,7 +8,8 @@ export interface IAgentMCPServerOptions { trainEndpoint?: string; }; studio?: { - name: string; + name?: string; apiKey: string; + apiEndpoint: string; } } diff --git a/packages/server/tests/01-server.spec.ts b/packages/server/tests/credo/01-server.spec.ts similarity index 100% rename from packages/server/tests/01-server.spec.ts rename to packages/server/tests/credo/01-server.spec.ts diff --git a/packages/server/tests/02-did.spec.ts b/packages/server/tests/credo/02-did.spec copy.ts similarity index 100% rename from packages/server/tests/02-did.spec.ts rename to packages/server/tests/credo/02-did.spec copy.ts diff --git a/packages/server/tests/credo/02-did.spec.ts b/packages/server/tests/credo/02-did.spec.ts new file mode 100644 index 0000000..c7d8d05 --- /dev/null +++ b/packages/server/tests/credo/02-did.spec.ts @@ -0,0 +1,120 @@ +import { randomUUID } from 'crypto'; +import { test, expect } from './setup'; +import { state } from './state'; + +test.describe('DID Operations', () => { + // Setup before all tests + test.beforeAll(async ({ client, parseToolResponse }) => { + // Create a DID for use in all tests + const result = await client.callTool({ + name: 'create-did', + arguments: { + network: 'testnet', + }, + }); + + const data = parseToolResponse(result); + expect(data).toHaveProperty('didState'); + expect(data.didState).toHaveProperty('state'); + expect(data.didState).toHaveProperty('did'); + + state.testDid = data.didState.did; + state.testDidDoc = data.didState.didDocument; + + expect(data.didState.state).toBe('finished'); + expect(data.didState.did).toMatch(/^did:cheqd:testnet:/); + expect(data.didState.didDocument.id).toBe(state.testDid); + expect(Array.isArray(data.didState.didDocument.verificationMethod)).toBe(true); + }); + + // Teardown after all tests in this suite + test.afterAll(async ({ shutdown }) => { + await shutdown(); + console.log('DID Operations test suite completed'); + }); + + test('should list DIDs in wallet', async ({ client, parseToolResponse }) => { + const result = await client.callTool({ + name: 'list-did', + arguments: {}, + }); + + const data = parseToolResponse(result); + expect(Array.isArray(data)).toBe(true); + expect(data.length).toBeGreaterThanOrEqual(1); + }); + + test('should resolve the created DID', async ({ client, parseToolResponse }) => { + // Resolve the DID + const resolveResult = await client.callTool({ + name: 'resolve-did', + arguments: { did: state.testDid }, + }); + + const resolveData = parseToolResponse(resolveResult); + expect(resolveData).toHaveProperty('id'); + expect(resolveData.id).toBe(state.testDid); + expect(resolveData).toHaveProperty('controller'); + expect(resolveData).toHaveProperty('verificationMethod'); + expect(resolveData).toHaveProperty('authentication'); + }); + test('should update the created DID', async ({ client, parseToolResponse }) => { + const didDoc = { + ...state.testDidDoc, + service: [ + { + id: `${state.testDid}#service-1`, + type: 'URL', + serviceEndpoint: ['https://example.com/vc/'], + }, + ], + }; + // Update the DID + const updateResult = await client.callTool({ + name: 'update-did', + arguments: { did: state.testDid, didDocument: didDoc }, + }); + + const updateData = parseToolResponse(updateResult); + expect(updateData).toHaveProperty('didState'); + expect(updateData.didState.state).toBe('finished'); + expect(updateData.didState.did).toBe(state.testDid); + expect(updateData.didState.didDocument.id).toBe(state.testDid); + expect(updateData.didState.didDocument).toHaveProperty('service'); + expect(Array.isArray(updateData.didState.didDocument.service)).toBe(true); + }); + + test.skip('should create a DID Linked Resource', async ({ client, parseToolResponse }) => { + const didSuffix = state.testDid.split(':').pop(); // Extract the last part of the DID + const params = { + id: randomUUID(), + name: 'TestResourceName', + resourceType: 'Document', + data: 'SGVsbG8gV29ybGQ=', // Base64 encoded data + collectionId: didSuffix, + version: '1.0', + }; + // Create DID Linked Resource + const createDLR = await client.callTool({ + name: 'create-did-linked-resource', + arguments: { did: state.testDid, ...params }, + }); + + const createData = parseToolResponse(createDLR); + expect(createData).toHaveProperty('resourceState'); + expect(createData.resourceState.state).toBe('finished'); + expect(createData.resourceState).toHaveProperty('resourceId'); + state.testDLRId = state.testDid + '/resources/' + createData.resourceState.resourceId; + }); + + test.skip('should resolve the created DID Linked Resource', async ({ client, parseToolResponse }) => { + // Resolve DID Linked Resource + const response = await client.callTool({ + name: 'resolve-did-linked-resource', + arguments: { didUrl: state.testDLRId }, + }); + + const result = parseToolResponse(response); + expect(result).toHaveProperty('resource'); + }); +}); diff --git a/packages/server/tests/03-anoncreds.spec.ts b/packages/server/tests/credo/03-anoncreds.spec.ts similarity index 100% rename from packages/server/tests/03-anoncreds.spec.ts rename to packages/server/tests/credo/03-anoncreds.spec.ts diff --git a/packages/server/tests/04-connection.spec.ts b/packages/server/tests/credo/04-connection.spec.ts similarity index 100% rename from packages/server/tests/04-connection.spec.ts rename to packages/server/tests/credo/04-connection.spec.ts diff --git a/packages/server/tests/05-credential.spec.ts b/packages/server/tests/credo/05-credential.spec.ts similarity index 100% rename from packages/server/tests/05-credential.spec.ts rename to packages/server/tests/credo/05-credential.spec.ts diff --git a/packages/server/tests/06-proof.spec.ts b/packages/server/tests/credo/06-proof.spec.ts similarity index 100% rename from packages/server/tests/06-proof.spec.ts rename to packages/server/tests/credo/06-proof.spec.ts diff --git a/packages/server/tests/setup.ts b/packages/server/tests/credo/setup.ts similarity index 99% rename from packages/server/tests/setup.ts rename to packages/server/tests/credo/setup.ts index 916c7bc..cfaedf6 100644 --- a/packages/server/tests/setup.ts +++ b/packages/server/tests/credo/setup.ts @@ -7,7 +7,7 @@ import { waitForCredentialExchangeState } from './utils'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const getServerPath = () => { - return path.resolve(__dirname, '../build/index.js'); + return path.resolve(__dirname, '../../build/index.js'); }; // Holder ACA-Py Agent API diff --git a/packages/server/tests/state.ts b/packages/server/tests/credo/state.ts similarity index 100% rename from packages/server/tests/state.ts rename to packages/server/tests/credo/state.ts diff --git a/packages/server/tests/utils.ts b/packages/server/tests/credo/utils.ts similarity index 98% rename from packages/server/tests/utils.ts rename to packages/server/tests/credo/utils.ts index 66ad852..4304c69 100644 --- a/packages/server/tests/utils.ts +++ b/packages/server/tests/credo/utils.ts @@ -3,7 +3,7 @@ import path from 'path'; // Helper function to start Docker containers and wait for them to be ready export async function startDockerServices(): Promise { - const dockerComposePath = path.resolve(__dirname, '../../../docker/docker-compose.yml'); + const dockerComposePath = path.resolve(__dirname, '../../../../docker/docker-compose.yml'); const process = spawn('docker', ['compose', '-f', dockerComposePath, '--profile', 'demo', 'up', '--detach']); return new Promise((resolve, reject) => { process.stdout?.on('data', (data) => console.log(`[Docker]: ${data.toString().trim()}`)); diff --git a/packages/server/tests/studio/01-server.spec.ts b/packages/server/tests/studio/01-server.spec.ts new file mode 100644 index 0000000..ac1e037 --- /dev/null +++ b/packages/server/tests/studio/01-server.spec.ts @@ -0,0 +1,43 @@ +import { test, expect } from './setup'; + +test.describe('Test Setup', () => { + // No setup or shutdown needed for this simple test group + + // Test for server initialization + test('should initialize server correctly', async ({ client }) => { + const response = await client.getServerVersion(); + expect(response).toBeDefined(); + expect(response?.name).toBe('cheqd-mcp-toolkit-server'); + }); + + // Test for server tools + // This test checks if the server has the expected tools available + test('check for expected tools', async ({ client }) => { + const tools = await client.listTools(); + expect(tools.tools).toBeDefined(); + // Extract the names of the tools + const toolNames = tools.tools.map((tool) => tool.name).filter(Boolean); + console.log(toolNames) + // Verify essential tools are available + const expectedTools = [ + 'create-did', + 'resolve-did', + 'update-did', + 'deactivate-did', + 'list-did', + 'create-did-linked-resource', + 'resolve-did-linked-resource', + 'list-credential-exchange-records', + 'issue-credential', + 'verify-credential', + 'revoke-credential', + 'suspend-credential', + 'reinstate-credential', + 'statuslist-create', + 'statuslist-update' + ]; + for (const toolName of expectedTools) { + expect(toolNames).toContain(toolName); + } + }); +}); diff --git a/packages/server/tests/studio/setup.ts b/packages/server/tests/studio/setup.ts new file mode 100644 index 0000000..8df6d4d --- /dev/null +++ b/packages/server/tests/studio/setup.ts @@ -0,0 +1,132 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { test as base } from '@playwright/test'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const getServerPath = () => { + return path.resolve(__dirname, '../../build/index.js'); +}; + +export interface TestFixtures { + client: Client; + parseToolResponse: (response: any) => any; + parseFlexibleToolResponse: (response: any) => any; + shutdown: () => Promise; +} + +let client: Client | null = null; +const startClient = async (): Promise => { + const env = { + ...process.env, + TOOLS: 'studio', + CHEQD_STUDIO_API_ENDPOINT: 'https://studio-api-staging.cheqd.net', + CHEQD_STUDIO_API_KEY: 'caas_01eeddee4d9c86aa3659f56a43c11cbbb348cbe62ac066e870f00f7ec75856795a77629428968bddb141ec14c148472aa01d178e9d749864d95cd765d583fecf' + }; + // Create MCP Client + const transport = new StdioClientTransport({ + command: 'node', + args: [getServerPath()], + env, + }); + client = new Client( + { name: 'test-client', version: '1.0.0' }, + { capabilities: { sampling: { tools: {} } } } + ); + await client.connect(transport); + return client; +}; +const shutdownClient = async (): Promise => { + try { + if (client) { + try { + await client.close(); + client = null; + } catch (e) { + console.error('Error closing client: ', e); + } + } + } catch (error) { + console.error('Error shutting down client:', error); + } +}; + +// Define custonm Playwright test with fixtures +export const test = base.extend({ + client: async ({}, use) => { + if (!client) { + client = await startClient(); + } + await use(client); + }, + + parseToolResponse: async ({}, use) => { + // Helper function to parse JSON from tool responses + const parseToolResponse = (response: any): any => { + try { + if (!response || !response.content || !response.content[0]) { + throw new Error('Invalid response structure'); + } + + const content = response.content[0]; + if (content.type !== 'text') { + throw new Error(`Unexpected content type: ${content.type}`); + } + + return JSON.parse(content.text); + } catch (e) { + console.error('Error parsing tool response:', e); + throw e; + } + }; + + await use(parseToolResponse); + }, + parseFlexibleToolResponse: async ({}, use) => { + const parseFlexibleToolResponse = (response: any): any => { + try { + if (!response || !response.content || !Array.isArray(response.content)) { + throw new Error('Invalid response structure'); + } + + // Parse the content array + const parsedResponse: any = {}; + + response.content.forEach((item: any) => { + if (item.type === 'text') { + // Handle text content + if (item.text.startsWith('{') && item.text.endsWith('}')) { + // Parse JSON text + parsedResponse.json = JSON.parse(item.text); + } else if (item.text.includes('Connection URL')) { + // Extract connection URL + parsedResponse.connectionUrl = item.text.split('Connection URL: ')[1]?.trim(); + } else { + parsedResponse.text = item.text; + } + } else if (item.type === 'image') { + // Handle image content + parsedResponse.image = { + data: item.data, + mimeType: item.mimeType, + }; + } else { + console.warn(`Unknown content type: ${item.type}`); + } + }); + return parsedResponse; + } catch (e) { + console.error('Error parsing tool response:', e); + throw e; + } + }; + await use(parseFlexibleToolResponse); + }, + shutdown: async ({}, use) => { + await use(async () => { + await shutdownClient(); + }); + }, +}); +export const { expect } = test; diff --git a/packages/studio/src/agent.ts b/packages/studio/src/agent.ts index cd5cd39..adaa4ff 100644 --- a/packages/studio/src/agent.ts +++ b/packages/studio/src/agent.ts @@ -1,18 +1,14 @@ import { IStudioToolKitOptions, CreateDidDocumentRequestType, UpdateDidDocumentRequestType, DeactivateDidDocumentRequestType, CreateDidDocumentResponseType, UpdateDidDocumentResponseType, DeactivateDidDocumentResponseType, CreateDidLinkedResourceRequestType, CreateDidLinkedResourceResponseType, IssueCredentialRequest, IssueCredentialResponseType, VerifyCredentialRequestType, CredentialStatusListCreateRequest, CredentialStatusListUpdateRequest } from './types.js'; -import { z } from 'zod' export class StudioAgent { - public name: string; + public name?: string; public endpoint: string; + private apiKey: string; - public constructor({ name }: IStudioToolKitOptions) { + public constructor({ name, apiKey, apiEndpoint }: IStudioToolKitOptions) { this.name = name; - - if(!process.env.STUDIO_API_ENDPOINT) { - throw new Error("Studio Endpoint Not initialized") - } - - this.endpoint = process.env.STUDIO_API_ENDPOINT; + this.apiKey = apiKey + this.endpoint = apiEndpoint || 'https://studio-api-staging.cheqd.net'; } /** @@ -22,7 +18,9 @@ export class StudioAgent { public dids = { resolveDidDocument: async (did: string) => { - const response = await fetch(`${this.endpoint}/did/search/${did}`); + const response = await fetch(`${this.endpoint}/did/search/${did}`, { + headers: { 'x-api-key': this.apiKey } + }); if (!response.ok) { throw new Error(`Failed to resolve DID: ${response.statusText}`); } @@ -32,7 +30,7 @@ export class StudioAgent { create: async(body: CreateDidDocumentRequestType): Promise => { const response = await fetch(`${this.endpoint}/did/create`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -44,7 +42,7 @@ export class StudioAgent { update: async(body: UpdateDidDocumentRequestType): Promise => { const response = await fetch(`${this.endpoint}/did/update`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -56,7 +54,7 @@ export class StudioAgent { deactivate: async(body: DeactivateDidDocumentRequestType): Promise => { const response = await fetch(`${this.endpoint}/did/deactivate`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -66,7 +64,9 @@ export class StudioAgent { }, getCreatedDids: async(params?: string): Promise => { - const response = await fetch(`${this.endpoint}/did/list${params}`); + const response = await fetch(`${this.endpoint}/did/list${params}`, { + headers: { 'x-api-key': this.apiKey } + }); if (!response.ok) { throw new Error(`Failed to list DID: ${response.statusText}`); } @@ -78,7 +78,7 @@ export class StudioAgent { createResource: async(body: CreateDidLinkedResourceRequestType): Promise => { const response = await fetch(`${this.endpoint}/resource/create`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -88,7 +88,9 @@ export class StudioAgent { }, resolveResource: async (didUrl: string) => { - const response = await fetch(`${this.endpoint}/resource/search/${didUrl}`); + const response = await fetch(`${this.endpoint}/resource/search/${didUrl}`, { + headers: { 'x-api-key': this.apiKey } + }); if (!response.ok) { throw new Error(`Failed to resolve DID: ${response.statusText}`); } @@ -101,7 +103,7 @@ export class StudioAgent { create: async(body: CredentialStatusListCreateRequest) => { const response = await fetch(`${this.endpoint}/credential-status/create/unencrypted`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -113,7 +115,7 @@ export class StudioAgent { update: async(body: CredentialStatusListUpdateRequest) => { const response = await fetch(`${this.endpoint}/credential-status/update/unencrypted`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -129,7 +131,7 @@ export class StudioAgent { issue: async(body: IssueCredentialRequest): Promise => { const response = await fetch(`${this.endpoint}/credential/issue`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -141,7 +143,7 @@ export class StudioAgent { verify: async(body: VerifyCredentialRequestType) => { const response = await fetch(`${this.endpoint}/credential/verify`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -151,7 +153,9 @@ export class StudioAgent { }, getCredentialExchangeRecords: async(params?: string) => { - const response = await fetch(`${this.endpoint}/did/list?${params}`); + const response = await fetch(`${this.endpoint}/did/list?${params}`, { + headers: { 'x-api-key': this.apiKey } + }); if (!response.ok) { throw new Error(`Failed to list DID: ${response.statusText}`); } @@ -162,7 +166,7 @@ export class StudioAgent { revoke: async(body) => { const response = await fetch(`${this.endpoint}/credential/revoke`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -174,7 +178,7 @@ export class StudioAgent { reinstate: async(body) => { const response = await fetch(`${this.endpoint}/credential/reinstate`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { @@ -186,7 +190,7 @@ export class StudioAgent { suspend: async(body) => { const response = await fetch(`${this.endpoint}/credential/suspend`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) }); if (!response.ok) { diff --git a/packages/studio/src/index.ts b/packages/studio/src/index.ts index 8258604..ca507dd 100644 --- a/packages/studio/src/index.ts +++ b/packages/studio/src/index.ts @@ -17,8 +17,8 @@ export class StudioToolKit { * @param {IStudioToolKitOptions} options - Configuration options for the toolkit * @param {string} options.name - Name of the agent */ - constructor({ name, apiKey }: IStudioToolKitOptions) { - this.studio = new StudioAgent({ name, apiKey }) + constructor({ name, apiKey, apiEndpoint }: IStudioToolKitOptions) { + this.studio = new StudioAgent({ name, apiKey, apiEndpoint }) } /// Initializes the Studio agent and prepares it for use. diff --git a/packages/studio/src/types.ts b/packages/studio/src/types.ts index 5b240e6..1b9a77e 100644 --- a/packages/studio/src/types.ts +++ b/packages/studio/src/types.ts @@ -16,8 +16,9 @@ export interface ToolDefinition { * Configuration options for initializing the Studio toolkit. */ export interface IStudioToolKitOptions { - name: string; + name?: string; apiKey: string; + apiEndpoint: string; } /** diff --git a/playwright.global-setup.ts b/playwright.global-setup.ts index af6acc0..f6c4957 100644 --- a/playwright.global-setup.ts +++ b/playwright.global-setup.ts @@ -1,4 +1,4 @@ -import { startDockerServices } from './packages/server/tests/utils'; +import { startDockerServices } from './packages/server/tests/credo/utils'; async function globalSetup() { await startDockerServices(); diff --git a/playwright.global-teardown.ts b/playwright.global-teardown.ts index ce78cd2..90e6693 100644 --- a/playwright.global-teardown.ts +++ b/playwright.global-teardown.ts @@ -1,4 +1,4 @@ -import { stopDockerServices } from './packages/server/tests/utils'; +import { stopDockerServices } from './packages/server/tests/credo/utils'; async function globalTeardown() { await stopDockerServices(); From 1eed3912672f8a3f7a6b9ac7764c2517a2708282 Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Tue, 3 Feb 2026 06:45:13 +0530 Subject: [PATCH 09/10] Add tests --- .../server/tests/credo/02-did.spec copy.ts | 120 ------------------ packages/server/tests/studio/02-did.spec.ts | 120 ++++++++++++++++++ packages/server/tests/studio/state.ts | 10 ++ packages/studio/src/agent.ts | 6 +- packages/studio/src/types.ts | 85 ++++++++++--- 5 files changed, 201 insertions(+), 140 deletions(-) delete mode 100644 packages/server/tests/credo/02-did.spec copy.ts create mode 100644 packages/server/tests/studio/02-did.spec.ts create mode 100644 packages/server/tests/studio/state.ts diff --git a/packages/server/tests/credo/02-did.spec copy.ts b/packages/server/tests/credo/02-did.spec copy.ts deleted file mode 100644 index c7d8d05..0000000 --- a/packages/server/tests/credo/02-did.spec copy.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { randomUUID } from 'crypto'; -import { test, expect } from './setup'; -import { state } from './state'; - -test.describe('DID Operations', () => { - // Setup before all tests - test.beforeAll(async ({ client, parseToolResponse }) => { - // Create a DID for use in all tests - const result = await client.callTool({ - name: 'create-did', - arguments: { - network: 'testnet', - }, - }); - - const data = parseToolResponse(result); - expect(data).toHaveProperty('didState'); - expect(data.didState).toHaveProperty('state'); - expect(data.didState).toHaveProperty('did'); - - state.testDid = data.didState.did; - state.testDidDoc = data.didState.didDocument; - - expect(data.didState.state).toBe('finished'); - expect(data.didState.did).toMatch(/^did:cheqd:testnet:/); - expect(data.didState.didDocument.id).toBe(state.testDid); - expect(Array.isArray(data.didState.didDocument.verificationMethod)).toBe(true); - }); - - // Teardown after all tests in this suite - test.afterAll(async ({ shutdown }) => { - await shutdown(); - console.log('DID Operations test suite completed'); - }); - - test('should list DIDs in wallet', async ({ client, parseToolResponse }) => { - const result = await client.callTool({ - name: 'list-did', - arguments: {}, - }); - - const data = parseToolResponse(result); - expect(Array.isArray(data)).toBe(true); - expect(data.length).toBeGreaterThanOrEqual(1); - }); - - test('should resolve the created DID', async ({ client, parseToolResponse }) => { - // Resolve the DID - const resolveResult = await client.callTool({ - name: 'resolve-did', - arguments: { did: state.testDid }, - }); - - const resolveData = parseToolResponse(resolveResult); - expect(resolveData).toHaveProperty('id'); - expect(resolveData.id).toBe(state.testDid); - expect(resolveData).toHaveProperty('controller'); - expect(resolveData).toHaveProperty('verificationMethod'); - expect(resolveData).toHaveProperty('authentication'); - }); - test('should update the created DID', async ({ client, parseToolResponse }) => { - const didDoc = { - ...state.testDidDoc, - service: [ - { - id: `${state.testDid}#service-1`, - type: 'URL', - serviceEndpoint: ['https://example.com/vc/'], - }, - ], - }; - // Update the DID - const updateResult = await client.callTool({ - name: 'update-did', - arguments: { did: state.testDid, didDocument: didDoc }, - }); - - const updateData = parseToolResponse(updateResult); - expect(updateData).toHaveProperty('didState'); - expect(updateData.didState.state).toBe('finished'); - expect(updateData.didState.did).toBe(state.testDid); - expect(updateData.didState.didDocument.id).toBe(state.testDid); - expect(updateData.didState.didDocument).toHaveProperty('service'); - expect(Array.isArray(updateData.didState.didDocument.service)).toBe(true); - }); - - test.skip('should create a DID Linked Resource', async ({ client, parseToolResponse }) => { - const didSuffix = state.testDid.split(':').pop(); // Extract the last part of the DID - const params = { - id: randomUUID(), - name: 'TestResourceName', - resourceType: 'Document', - data: 'SGVsbG8gV29ybGQ=', // Base64 encoded data - collectionId: didSuffix, - version: '1.0', - }; - // Create DID Linked Resource - const createDLR = await client.callTool({ - name: 'create-did-linked-resource', - arguments: { did: state.testDid, ...params }, - }); - - const createData = parseToolResponse(createDLR); - expect(createData).toHaveProperty('resourceState'); - expect(createData.resourceState.state).toBe('finished'); - expect(createData.resourceState).toHaveProperty('resourceId'); - state.testDLRId = state.testDid + '/resources/' + createData.resourceState.resourceId; - }); - - test.skip('should resolve the created DID Linked Resource', async ({ client, parseToolResponse }) => { - // Resolve DID Linked Resource - const response = await client.callTool({ - name: 'resolve-did-linked-resource', - arguments: { didUrl: state.testDLRId }, - }); - - const result = parseToolResponse(response); - expect(result).toHaveProperty('resource'); - }); -}); diff --git a/packages/server/tests/studio/02-did.spec.ts b/packages/server/tests/studio/02-did.spec.ts new file mode 100644 index 0000000..4ee1f87 --- /dev/null +++ b/packages/server/tests/studio/02-did.spec.ts @@ -0,0 +1,120 @@ +import { randomUUID } from 'crypto'; +import { test, expect } from './setup'; +import { state } from './state'; +import { CreateDidDocumentResponseType, CreateDidLinkedResourceResponse, UpdateDidDocumentResponseType } from '@cheqd/mcp-toolkit-studio/src/types'; +import { CreateDidLinkedResourceResponseType } from '@cheqd/mcp-toolkit-studio/build/types'; + +test.describe('DID Operations', () => { + // Setup before all tests + test.beforeAll(async ({ client, parseToolResponse }) => { + // Create a DID for use in all tests + const result = await client.callTool({ + name: 'create-did', + arguments: { + network: 'testnet', + }, + }); + + const data: CreateDidDocumentResponseType = parseToolResponse(result); + expect(data).toHaveProperty('did'); + expect(data).toHaveProperty('keys'); + + state.testDid = data.did; + state.testDidDoc = data; + + expect(data.did).toMatch(/^did:cheqd:testnet:/); + expect(Array.isArray(data.keys)).toBe(true); + }); + + // Teardown after all tests in this suite + test.afterAll(async ({ shutdown }) => { + await shutdown(); + console.log('DID Operations test suite completed'); + }); + + test('should list DIDs in wallet', async ({ client, parseToolResponse }) => { + const result = await client.callTool({ + name: 'list-did', + arguments: {} + }); + const data = parseToolResponse(result); + expect(Array.isArray(data.dids)).toBe(true); + expect(data.total).toBeGreaterThanOrEqual(1); + }); + + test('should resolve the created DID', async ({ client, parseToolResponse }) => { + // Resolve the DID + const resolveResult = await client.callTool({ + name: 'resolve-did', + arguments: { did: state.testDid }, + }); + + const resolveData = parseToolResponse(resolveResult); + expect(resolveData).toHaveProperty('didDocument'); + expect(resolveData.didDocument).toHaveProperty('id'); + expect(resolveData.didDocument.id).toBe(state.testDid); + expect(resolveData.didDocument).toHaveProperty('controller'); + expect(resolveData.didDocument).toHaveProperty('verificationMethod'); + expect(resolveData.didDocument).toHaveProperty('authentication'); + expect(Array.isArray(resolveData.didDocument.verificationMethod)).toBe(true); + + state.testDidDoc = resolveData.didDocument + }); + test('should update the created DID', async ({ client, parseToolResponse }) => { + const didDoc = { + ...state.testDidDoc, + service: [ + { + id: `${state.testDid}#service-1`, + type: 'URL', + serviceEndpoint: ['https://example.com/vc/'], + }, + ], + }; + // Update the DID + const updateResult = await client.callTool({ + name: 'update-did', + arguments: { did: state.testDid, didDocument: didDoc }, + }); + + const updateData: UpdateDidDocumentResponseType = parseToolResponse(updateResult); + expect(updateData).toHaveProperty('did'); + expect(updateData.did).toBe(state.testDid); + expect(updateData).toHaveProperty('services'); + expect(Array.isArray(updateData.services)).toBe(true); + }); + + test('should create a DID Linked Resource', async ({ client, parseToolResponse }) => { + const params = { + did: state.testDid, + name: 'TestResourceName', + type: 'Document', + encoding: 'base64', + data: 'SGVsbG8gV29ybGQ=', // Base64 encoded data + version: '1.0', + }; + // Create DID Linked Resource + const createDLR = await client.callTool({ + name: 'create-did-linked-resource', + arguments: params, + }); + + const createData: CreateDidLinkedResourceResponseType = parseToolResponse(createDLR); + expect(createData.resource).toHaveProperty('resourceId'); + expect(createData.resource).toHaveProperty('resourceName'); + expect(createData.resource.resourceId).toBeTruthy(); + state.testDLRId = state.testDid + '?resourceId=' + createData.resource.resourceId; + }); + + test('should resolve the created DID Linked Resource', async ({ client, parseToolResponse }) => { + // Resolve DID Linked Resource + const response = await client.callTool({ + name: 'resolve-did-linked-resource', + arguments: { didUrl: state.testDLRId }, + }); + + const result = parseToolResponse(response) + const decodedText = Buffer.from(Object.values(result as any)).toString('utf-8'); + expect(decodedText).toBe('Hello World'); + }); +}); diff --git a/packages/server/tests/studio/state.ts b/packages/server/tests/studio/state.ts new file mode 100644 index 0000000..e9fb3f4 --- /dev/null +++ b/packages/server/tests/studio/state.ts @@ -0,0 +1,10 @@ +export const state = { + testDid: '', + testDidDoc: {}, + testDLRId: '', + testSchemaId: '', + testCredentialDefinitionId: '', + connectionId: '', + credentialExchangeId: '', + proofRecordId: '', +}; diff --git a/packages/studio/src/agent.ts b/packages/studio/src/agent.ts index adaa4ff..9f3ee5f 100644 --- a/packages/studio/src/agent.ts +++ b/packages/studio/src/agent.ts @@ -64,7 +64,7 @@ export class StudioAgent { }, getCreatedDids: async(params?: string): Promise => { - const response = await fetch(`${this.endpoint}/did/list${params}`, { + const response = await fetch(`${this.endpoint}/did/list?${params}`, { headers: { 'x-api-key': this.apiKey } }); if (!response.ok) { @@ -76,7 +76,7 @@ export class StudioAgent { public resources = { createResource: async(body: CreateDidLinkedResourceRequestType): Promise => { - const response = await fetch(`${this.endpoint}/resource/create`, { + const response = await fetch(`${this.endpoint}/resource/create/${body.did}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, body: JSON.stringify(body) @@ -94,7 +94,7 @@ export class StudioAgent { if (!response.ok) { throw new Error(`Failed to resolve DID: ${response.statusText}`); } - return await response.json(); + return await response.bytes(); }, } diff --git a/packages/studio/src/types.ts b/packages/studio/src/types.ts index 1b9a77e..03eb8aa 100644 --- a/packages/studio/src/types.ts +++ b/packages/studio/src/types.ts @@ -47,18 +47,6 @@ export const CreateDidDocumentParams = { verificationMethodType: z.enum(['Ed25519VerificationKey2018', 'JsonWebKey2020', 'Ed25519VerificationKey2020']).optional().default('Ed25519VerificationKey2020').describe('Type of verification method for the DID'), }; -const CreateDidDocumentShape = z.object(CreateDidDocumentParams) -export type CreateDidDocumentRequestType = z.infer; - -export const CreateDidDocumentResponse = z.object({ - did: z.string().describe('The DID'), - didDocument: z.any().describe('The full DID Document'), - success: z.boolean().describe('Whether the operation was successful'), - existedInDb: z.boolean().describe('Whether the DID was retrieved from local database'), - error: z.string().optional().describe('Error message if creation failed'), -}) -export type CreateDidDocumentResponseType = z.infer; - /** * JSON Web Key schema for cryptographic keys */ @@ -89,6 +77,37 @@ const DidDocumentServiceSchema = z.object({ serviceEndpoint: z.union([z.string(), z.array(z.string())]), }); +const CreateDidDocumentShape = z.object(CreateDidDocumentParams) +export type CreateDidDocumentRequestType = z.infer; + +export const CreateDidDocumentResponse = z.object({ + did: z.string().describe('The DID'), + keys: z.array(z.object({ + kid: z.string(), + kms: z.string(), + type: z.string(), + publicKeyHex: z.string(), + meta: z.record(z.unknown()), + controller: z.string(), + })).describe('Array of cryptographic keys associated with the DID'), + services: z.array(DidDocumentServiceSchema).describe('Array of service endpoints'), + provider: z.string().describe('The DID provider'), + controllerKeyRefs: z.array(z.string()).describe('Array of key identifiers controlling the DID'), + controllerKeys: z.array(z.object({ + kid: z.string(), + kms: z.string(), + type: z.string(), + publicKeyHex: z.string(), + meta: z.record(z.unknown()), + controller: z.string(), + })).describe('Array of controller keys'), + controllerKeyId: z.string().describe('The key identifier used for signing'), + success: z.boolean().describe('Whether the operation was successful'), + existedInDb: z.boolean().describe('Whether the DID was retrieved from local database'), + error: z.string().optional().describe('Error message if creation failed'), +}) +export type CreateDidDocumentResponseType = z.infer; + /** * Complete schema for a DID Document */ @@ -118,10 +137,27 @@ const UpdateDidDocumentShape = z.object(UpdateDidDocumentParams) export type UpdateDidDocumentRequestType = z.infer; export const UpdateDidDocumentResponse = z.object({ - did: z.string().describe('The updated DID'), - didDocument: DidDocumentSchema, - success: z.boolean().describe('Whether the update was successful'), - error: z.string().optional().describe('Error message if update failed'), + did: z.string().describe('The updated DID'), + keys: z.array(z.object({ + kid: z.string(), + kms: z.string(), + type: z.string(), + publicKeyHex: z.string(), + meta: z.record(z.unknown()), + controller: z.string(), + })).describe('Array of cryptographic keys associated with the DID'), + services: z.array(DidDocumentServiceSchema).describe('Array of service endpoints'), + provider: z.string().describe('The DID provider'), + controllerKeyRefs: z.array(z.string()).describe('Array of key identifiers controlling the DID'), + controllerKeys: z.array(z.object({ + kid: z.string(), + kms: z.string(), + type: z.string(), + publicKeyHex: z.string(), + meta: z.record(z.unknown()), + controller: z.string(), + })).describe('Array of controller keys'), + controllerKeyId: z.string().describe('The key identifier used for signing'), }) export type UpdateDidDocumentResponseType = z.infer; @@ -168,7 +204,22 @@ export const CreateDidLinkedResourceParams = { const CreateDidLinkedResourceShape = z.object(CreateDidLinkedResourceParams) export type CreateDidLinkedResourceRequestType = z.infer; -export type CreateDidLinkedResourceResponseType = any; +export const CreateDidLinkedResourceResponse = z.object({ + resource: z.object({ + checksum: z.string().describe('SHA-256 checksum of the resource'), + created: z.string().datetime().describe('ISO 8601 timestamp when resource was created'), + mediaType: z.string().describe('MIME type of the resource'), + nextVersionId: z.string().nullable().describe('ID of the next version if available'), + previousVersionId: z.string().nullable().describe('ID of the previous version if available'), + resourceCollectionId: z.string().uuid().describe('UUID of the resource collection (DID without method)'), + resourceId: z.string().uuid().describe('UUID of the resource'), + resourceName: z.string().describe('Human-readable name of the resource'), + resourceType: z.string().describe('Type of resource'), + resourceURI: DID_URL.describe('Full DID URL pointing to the resource'), + resourceVersion: z.string().describe('Human-readable version of the resource'), + }).describe('The created DID-Linked Resource'), +}); +export type CreateDidLinkedResourceResponseType = z.infer; export const IssueCredentialParams = { issuerDid: z.string().describe('DID of the Verifiable Credential issuer. This needs to be a `did:cheqd` DID.'), From 705370f7b4fdcef45750a3174757c7fcb77f1e6e Mon Sep 17 00:00:00 2001 From: DaevMithran Date: Tue, 3 Feb 2026 10:53:53 +0530 Subject: [PATCH 10/10] Update response types to ApiResponse --- packages/server/tests/studio/02-did.spec.ts | 30 +- packages/studio/src/agent.ts | 324 ++++++++++++-------- 2 files changed, 224 insertions(+), 130 deletions(-) diff --git a/packages/server/tests/studio/02-did.spec.ts b/packages/server/tests/studio/02-did.spec.ts index 4ee1f87..adec0ce 100644 --- a/packages/server/tests/studio/02-did.spec.ts +++ b/packages/server/tests/studio/02-did.spec.ts @@ -15,7 +15,10 @@ test.describe('DID Operations', () => { }, }); - const data: CreateDidDocumentResponseType = parseToolResponse(result); + const resp = parseToolResponse(result) as any; + expect(resp).toHaveProperty('success'); + expect(resp.success).toBeTruthy(); + const data: CreateDidDocumentResponseType = resp.data; expect(data).toHaveProperty('did'); expect(data).toHaveProperty('keys'); @@ -37,7 +40,10 @@ test.describe('DID Operations', () => { name: 'list-did', arguments: {} }); - const data = parseToolResponse(result); + const listResp = parseToolResponse(result) as any; + expect(listResp).toHaveProperty('success'); + expect(listResp.success).toBeTruthy(); + const data = listResp.data; expect(Array.isArray(data.dids)).toBe(true); expect(data.total).toBeGreaterThanOrEqual(1); }); @@ -49,7 +55,10 @@ test.describe('DID Operations', () => { arguments: { did: state.testDid }, }); - const resolveData = parseToolResponse(resolveResult); + const resolveResp = parseToolResponse(resolveResult) as any; + expect(resolveResp).toHaveProperty('success'); + expect(resolveResp.success).toBeTruthy(); + const resolveData = resolveResp.data; expect(resolveData).toHaveProperty('didDocument'); expect(resolveData.didDocument).toHaveProperty('id'); expect(resolveData.didDocument.id).toBe(state.testDid); @@ -77,7 +86,10 @@ test.describe('DID Operations', () => { arguments: { did: state.testDid, didDocument: didDoc }, }); - const updateData: UpdateDidDocumentResponseType = parseToolResponse(updateResult); + const updateResp = parseToolResponse(updateResult) as any; + expect(updateResp).toHaveProperty('success'); + expect(updateResp.success).toBeTruthy(); + const updateData: UpdateDidDocumentResponseType = updateResp.data; expect(updateData).toHaveProperty('did'); expect(updateData.did).toBe(state.testDid); expect(updateData).toHaveProperty('services'); @@ -99,7 +111,10 @@ test.describe('DID Operations', () => { arguments: params, }); - const createData: CreateDidLinkedResourceResponseType = parseToolResponse(createDLR); + const createResp = parseToolResponse(createDLR) as any; + expect(createResp).toHaveProperty('success'); + expect(createResp.success).toBeTruthy(); + const createData: CreateDidLinkedResourceResponseType = createResp.data; expect(createData.resource).toHaveProperty('resourceId'); expect(createData.resource).toHaveProperty('resourceName'); expect(createData.resource.resourceId).toBeTruthy(); @@ -113,7 +128,10 @@ test.describe('DID Operations', () => { arguments: { didUrl: state.testDLRId }, }); - const result = parseToolResponse(response) + const resp = parseToolResponse(response) as any; + expect(resp).toHaveProperty('success'); + expect(resp.success).toBeTruthy(); + const result = resp.data; const decodedText = Buffer.from(Object.values(result as any)).toString('utf-8'); expect(decodedText).toBe('Hello World'); }); diff --git a/packages/studio/src/agent.ts b/packages/studio/src/agent.ts index 9f3ee5f..4708e04 100644 --- a/packages/studio/src/agent.ts +++ b/packages/studio/src/agent.ts @@ -1,5 +1,13 @@ import { IStudioToolKitOptions, CreateDidDocumentRequestType, UpdateDidDocumentRequestType, DeactivateDidDocumentRequestType, CreateDidDocumentResponseType, UpdateDidDocumentResponseType, DeactivateDidDocumentResponseType, CreateDidLinkedResourceRequestType, CreateDidLinkedResourceResponseType, IssueCredentialRequest, IssueCredentialResponseType, VerifyCredentialRequestType, CredentialStatusListCreateRequest, CredentialStatusListUpdateRequest } from './types.js'; +// Generic API response wrapper used across StudioAgent methods +export type ApiResponse = { + success: boolean; + error?: string; + status?: number; + data?: T; +}; + export class StudioAgent { public name?: string; public endpoint: string; @@ -17,186 +25,254 @@ export class StudioAgent { public async initializeAgent() {} public dids = { - resolveDidDocument: async (did: string) => { - const response = await fetch(`${this.endpoint}/did/search/${did}`, { - headers: { 'x-api-key': this.apiKey } - }); - if (!response.ok) { - throw new Error(`Failed to resolve DID: ${response.statusText}`); + resolveDidDocument: async (did: string): Promise> => { + try { + const res = await fetch(`${this.endpoint}/did/search/${did}`, { headers: { 'x-api-key': this.apiKey } }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, - create: async(body: CreateDidDocumentRequestType): Promise => { - const response = await fetch(`${this.endpoint}/did/create`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to create DID: ${response.statusText}`); + create: async(body: CreateDidDocumentRequestType): Promise> => { + try { + const res = await fetch(`${this.endpoint}/did/create`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, - update: async(body: UpdateDidDocumentRequestType): Promise => { - const response = await fetch(`${this.endpoint}/did/update`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to update DID: ${response.statusText}`); + update: async(body: UpdateDidDocumentRequestType): Promise> => { + try { + const res = await fetch(`${this.endpoint}/did/update`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, - deactivate: async(body: DeactivateDidDocumentRequestType): Promise => { - const response = await fetch(`${this.endpoint}/did/deactivate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to deactivate DID: ${response.statusText}`); + deactivate: async(body: DeactivateDidDocumentRequestType): Promise> => { + try { + const res = await fetch(`${this.endpoint}/did/deactivate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, - getCreatedDids: async(params?: string): Promise => { - const response = await fetch(`${this.endpoint}/did/list?${params}`, { - headers: { 'x-api-key': this.apiKey } - }); - if (!response.ok) { - throw new Error(`Failed to list DID: ${response.statusText}`); + getCreatedDids: async(params?: string): Promise> => { + try { + const res = await fetch(`${this.endpoint}/did/list?${params}`, { headers: { 'x-api-key': this.apiKey } }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); } }; public resources = { - createResource: async(body: CreateDidLinkedResourceRequestType): Promise => { - const response = await fetch(`${this.endpoint}/resource/create/${body.did}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to create DID Linked Resource: ${response.statusText}`); + createResource: async(body: CreateDidLinkedResourceRequestType): Promise> => { + try { + const res = await fetch(`${this.endpoint}/resource/create/${body.did}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, - resolveResource: async (didUrl: string) => { - const response = await fetch(`${this.endpoint}/resource/search/${didUrl}`, { - headers: { 'x-api-key': this.apiKey } - }); - if (!response.ok) { - throw new Error(`Failed to resolve DID: ${response.statusText}`); + resolveResource: async (didUrl: string): Promise>> => { + try { + const res = await fetch(`${this.endpoint}/resource/search/${didUrl}`, { headers: { 'x-api-key': this.apiKey } }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const buf = await res.bytes(); + return { success: true, status: res.status, data: buf }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.bytes(); }, } public statusList = { - create: async(body: CredentialStatusListCreateRequest) => { - const response = await fetch(`${this.endpoint}/credential-status/create/unencrypted`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to create DID Linked Resource: ${response.statusText}`); + create: async(body: CredentialStatusListCreateRequest): Promise> => { + try { + const res = await fetch(`${this.endpoint}/credential-status/create/unencrypted`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, - update: async(body: CredentialStatusListUpdateRequest) => { - const response = await fetch(`${this.endpoint}/credential-status/update/unencrypted`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to create DID Linked Resource: ${response.statusText}`); + update: async(body: CredentialStatusListUpdateRequest): Promise> => { + try { + const res = await fetch(`${this.endpoint}/credential-status/update/unencrypted`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, } public credentials = { - issue: async(body: IssueCredentialRequest): Promise => { - const response = await fetch(`${this.endpoint}/credential/issue`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to issue Credential: ${response.statusText}`); + issue: async(body: IssueCredentialRequest): Promise> => { + try { + const res = await fetch(`${this.endpoint}/credential/issue`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, - verify: async(body: VerifyCredentialRequestType) => { - const response = await fetch(`${this.endpoint}/credential/verify`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to issue Credential: ${response.statusText}`); + verify: async(body: VerifyCredentialRequestType): Promise> => { + try { + const res = await fetch(`${this.endpoint}/credential/verify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, - getCredentialExchangeRecords: async(params?: string) => { - const response = await fetch(`${this.endpoint}/did/list?${params}`, { - headers: { 'x-api-key': this.apiKey } - }); - if (!response.ok) { - throw new Error(`Failed to list DID: ${response.statusText}`); + getCredentialExchangeRecords: async(params?: string): Promise> => { + try { + const res = await fetch(`${this.endpoint}/did/list?${params}`, { headers: { 'x-api-key': this.apiKey } }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, revoke: async(body) => { - const response = await fetch(`${this.endpoint}/credential/revoke`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to issue Credential: ${response.statusText}`); + try { + const res = await fetch(`${this.endpoint}/credential/revoke`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, reinstate: async(body) => { - const response = await fetch(`${this.endpoint}/credential/reinstate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to issue Credential: ${response.statusText}`); + try { + const res = await fetch(`${this.endpoint}/credential/reinstate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, suspend: async(body) => { - const response = await fetch(`${this.endpoint}/credential/suspend`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(`Failed to issue Credential: ${response.statusText}`); + try { + const res = await fetch(`${this.endpoint}/credential/suspend`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey }, + body: JSON.stringify(body) + }); + if (!res.ok) { + + return { success: false, status: res.status, error: res.statusText }; + } + const json = await res.json().catch((e) => undefined); + return { success: true, status: res.status, data: json }; + } catch (err: any) { + return { success: false, error: err?.message ?? String(err) }; } - return await response.json(); }, }