Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/remote-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 8 additions & 2 deletions packages/remote-server/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,21 @@ 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),
apiEndpoint: normalizeEnvVar(process.env.CHEQD_STUDIO_API_ENDPOINT)
} : undefined
});
// Initializing the server with tools
this.server.setupTools().catch((err) => {
Expand Down
4 changes: 4 additions & 0 deletions packages/remote-server/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export interface IAgentMCPServerOptions {
domain?: string;
cosmosPayerSeed?: string;
};
studio?: {
name: string;
apiKey: string;
}
}
1 change: 1 addition & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
],
"dependencies": {
"@cheqd/mcp-toolkit-credo": "1.5.1-develop.1",
"@cheqd/mcp-toolkit-studio": "workspace:*",
"@modelcontextprotocol/sdk": "^1.20.2",
"zod": "^3.25.56"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
39 changes: 38 additions & 1 deletion packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -159,6 +169,31 @@ export class AgentMcpServer extends McpServer {
}
}

/**
* Set up Credo-specific tools
*/
private async setupStudioTools(tools: ToolDefinition<any>[]): Promise<void> {
// Validate required env variables
if (!this.options.studio?.apiKey || !this.options.studio.apiEndpoint) {
throw new Error(
'Missing required environment variables for Studio tools. Please set: CHEQD_STUDIO_API_KEY'
);
}
try {
this.studioToolkit = new StudioToolKit({
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();

tools.push(...(studioTools as ToolDefinition<any>[]));
} catch (err) {
throw new Error(`Studio initialization failed: ${err instanceof Error ? err.message : String(err)}`);
}
}

/*
* Start the server and connect to the specified transport
*/
Expand All @@ -183,11 +218,13 @@ export class AgentMcpServer extends McpServer {
async cleanup(): Promise<void> {
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
Expand Down
2 changes: 2 additions & 0 deletions packages/server/src/types/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/server/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ export interface IAgentMCPServerOptions {
cosmosPayerSeed?: string;
trainEndpoint?: string;
};
studio?: {
name?: string;
apiKey: string;
apiEndpoint: string;
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChildProcess> {
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()}`));
Expand Down
43 changes: 43 additions & 0 deletions packages/server/tests/studio/01-server.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
}
});
});
138 changes: 138 additions & 0 deletions packages/server/tests/studio/02-did.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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 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');

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 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);
});

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 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);
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 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');
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 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();
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 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');
});
});
Loading
Loading