Skip to main content
Glama
MIT License
212
15
  • Apple
  • Linux
import { StdioClientTransport, StdioServerParameters } from '@modelcontextprotocol/sdk/client/stdio.js' import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { CallToolRequest, JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js' import path from 'path' const RESPONSE_TIMEOUT = 1_000 // 1s const START_DELAY = 1_000 // 1s const TEST_TIMEOUT = 10_000 // 10s const TOTAL_TOOLS = 64 const streamableClientUrl = new URL(`http://localhost:${process.env.PORT || 3000}/mcp`) jest.setTimeout(TEST_TIMEOUT) type ReadMessageType = { jsonrpc: string id: number result: { content?: { type: string text: string }[], tools?: any[] } } type JSONRPCMessageWithParams = JSONRPCMessage & { params?: CallToolRequest["params"] } // In some tests the argument IDs in this object are modified, ensure the ID is always set per-test where relevant const jsonRpcMessage: Record<string, JSONRPCMessageWithParams> = { ping: { jsonrpc: "2.0", id: 1, method: "ping" }, pong: { jsonrpc: '2.0', id: 1, result: {} }, toolsList: { jsonrpc: "2.0", id: 1, method: "tools/list" }, getProfile: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "get_profile", arguments: {} } }, createDraft: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "create_draft", arguments: { subject: "Test Subject", body: "Test Body", to: ["test@example.com"], cc: ["test@example.com"], bcc: ["test@example.com"] } } }, listDrafts: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "list_drafts", arguments: { } } }, getDraft: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "get_draft", arguments: { id: "test-id" } } }, updateDraft: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "update_draft", arguments: { id: "test-id", subject: "Updated Subject", body: "Updated Body", to: ["updated@example.com"], cc: ["updated@example.com"], bcc: ["updated@example.com"] } } }, deleteDraft: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "delete_draft", arguments: { id: "test-id" } } } } const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) describe('Gmail MCP', () => { let stdioClient: StdioClientTransport let streamableClient: StreamableHTTPClientTransport beforeAll(async () => { const serverParameters: StdioServerParameters = { command: "node", args: [path.resolve(__dirname, '../dist/index.js')], env: process.env as Record<string, string> } stdioClient = new StdioClientTransport(serverParameters) await stdioClient.start() streamableClient = new StreamableHTTPClientTransport(streamableClientUrl) }) afterAll(async () => { await stdioClient.close() }) describe('Stdio Transport', () => { let readMessages: ReadMessageType[] let errors: Error[] let draftId: string beforeAll(async () => { await delay(START_DELAY) stdioClient.onmessage = (message) => readMessages.push(message as ReadMessageType) stdioClient.onerror = (error) => errors.push(error) }) beforeEach(async () => { readMessages = [] errors = [] }) it('responds to ping', async () => { stdioClient.send(jsonRpcMessage.ping) await delay(RESPONSE_TIMEOUT) expect(readMessages).toHaveLength(1) expect(readMessages[0]).toEqual(jsonRpcMessage.pong) expect(errors).toHaveLength(0) }) it('returns a list of tools', async () => { stdioClient.send(jsonRpcMessage.toolsList) await delay(RESPONSE_TIMEOUT) expect(readMessages).toHaveLength(1) expect(readMessages[0].result.tools?.length).toEqual(TOTAL_TOOLS) }) it('can call the get_profile tool', async () => { stdioClient.send(jsonRpcMessage.getProfile) await delay(RESPONSE_TIMEOUT) expect(readMessages).toHaveLength(1) expect(readMessages[0].result.content?.length).toEqual(1) const firstMessage = JSON.parse(readMessages[0].result.content?.[0].text ?? '{}') expect(firstMessage.emailAddress).toBeDefined() expect(firstMessage.messagesTotal).toBeDefined() expect(firstMessage.historyId).toBeDefined() }) it('can call the create_draft tool', async () => { stdioClient.send(jsonRpcMessage.createDraft) await delay(RESPONSE_TIMEOUT) expect(readMessages).toHaveLength(1) expect(readMessages[0].result.content?.length).toEqual(1) draftId = JSON.parse(readMessages[0].result.content?.[0].text ?? '{}').id expect(draftId).toBeDefined() }) it('can call the list_drafts tool', async () => { stdioClient.send(jsonRpcMessage.listDrafts) await delay(RESPONSE_TIMEOUT) expect(readMessages).toHaveLength(1) expect(readMessages[0].result.content?.length).toEqual(1) const draftsList = JSON.parse(readMessages[0].result.content?.[0].text ?? '{}') const createdDraft = draftsList.find((draft: any) => draft.id === draftId) expect(createdDraft).toBeDefined() }) it('can call the get_draft tool', async () => { const params = jsonRpcMessage.getDraft.params as CallToolRequest["params"] if (params?.arguments) params.arguments.id = draftId stdioClient.send(jsonRpcMessage.getDraft) await delay(RESPONSE_TIMEOUT) expect(readMessages).toHaveLength(1) expect(readMessages[0].result.content?.length).toEqual(1) const draftEmail = JSON.parse(readMessages[0].result.content?.[0].text ?? '{}') expect(draftEmail.message.labelIds).toContain("DRAFT") expect(draftEmail.message.snippet).toEqual("Test Body") }) it('can call the delete_draft tool', async () => { const params = jsonRpcMessage.deleteDraft.params as CallToolRequest["params"] if (params?.arguments) params.arguments.id = draftId stdioClient.send(jsonRpcMessage.deleteDraft) await delay(RESPONSE_TIMEOUT) expect(readMessages).toHaveLength(1) expect(readMessages[0].result.content?.length).toEqual(1) }) }) describe('Streamable HTTP Transport', () => { let streamableClient: Client = new Client({ name: 'streamable-test-client', version: '1.0.0' }) let draftId: string beforeAll(async () => { let transport = new StreamableHTTPClientTransport(streamableClientUrl) await streamableClient.connect(transport) }) it('responds to ping', async () => { const response = await streamableClient.ping() expect(response).toEqual({}) }) it('returns a list of tools', async () => { const response = await streamableClient.listTools() expect(response.tools.length).toEqual(TOTAL_TOOLS) }) it('can call the get_profile tool', async () => { const params = jsonRpcMessage.getProfile.params as CallToolRequest["params"] const response = await streamableClient.callTool(params) const firstMessage = JSON.parse(response.content?.[0].text ?? '{}') expect(firstMessage.emailAddress).toBeDefined() expect(firstMessage.messagesTotal).toBeDefined() expect(firstMessage.historyId).toBeDefined() }) it('can call the create_draft tool', async () => { const params = jsonRpcMessage.createDraft.params as CallToolRequest["params"] const response = await streamableClient.callTool(params) draftId = JSON.parse(response.content?.[0].text ?? '{}').id expect(draftId).toBeDefined() }) it('can call the list_drafts tool', async () => { const params = jsonRpcMessage.listDrafts.params as CallToolRequest["params"] const response = await streamableClient.callTool(params) const draftsList = JSON.parse(response.content?.[0].text ?? '{}') const createdDraft = draftsList.find((draft: any) => draft.id === draftId) expect(createdDraft).toBeDefined() }) it('can call the get_draft tool', async () => { const params = jsonRpcMessage.getDraft.params as CallToolRequest["params"] if (params?.arguments) params.arguments.id = draftId const response = await streamableClient.callTool(params) const draftEmail = JSON.parse(response.content?.[0].text ?? '{}') expect(draftEmail.message.labelIds).toContain("DRAFT") expect(draftEmail.message.snippet).toEqual("Test Body") }) // it('can call the update_draft tool', async () => { // const params = jsonRpcMessage.updateDraft.params as CallToolRequest["params"] // if (params?.arguments) params.arguments.id = draftId // const response = await streamableClient.callTool(params) // const updatedDraft = JSON.parse(response.content?.[0].text ?? '{}') // console.log(updatedDraft) // expect(updatedDraft.message.labelIds).toContain("DRAFT") // // expect(updatedDraft.message.snippet).toEqual("Updated Body") // TODO this test fails, update before uncommenting the update_draft method // }) it('can call the delete_draft tool', async () => { const params = jsonRpcMessage.deleteDraft.params as CallToolRequest["params"] if (params?.arguments) params.arguments.id = draftId const response = await streamableClient.callTool(params) expect(response.content?.[0].text).toEqual('\"\"') }) }) })

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shinzo-labs/gmail-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server