Integration Examples
This page walks through a complete TypeScript integration from connecting to MCPGate to invoking tools with full metadata-aware policy logic. The example uses the official MCP TypeScript SDK.
Prerequisites
npm install @modelcontextprotocol/sdk. You will also need an MCPGate API key from the Apps → API Keys page in the dashboard.Step-by-step walkthrough#
Connect to MCPGate
Create an SSE transport pointing at your MCPGate MCP endpoint and pass your API key as a bearer token. The endpoint is the same URL you paste into Claude Desktop or Cursor.
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
const transport = new SSEClientTransport(
new URL('https://mcp.mcpgate.io/mcp'),
{
requestInit: {
headers: {
Authorization: `Bearer ${process.env.MCPGATE_API_KEY}`,
},
},
}
)
const client = new Client({ name: 'my-agent', version: '1.0.0' }, { capabilities: {} })
await client.connect(transport)
console.log('Connected to MCPGate')Discover connected services
Call mcpgate_list_connectors to learn which services the user has connected. Use this to render an empty state if key services are missing.
const result = await client.callTool({
name: 'mcpgate_list_connectors',
arguments: {},
})
// Parse the JSON text content
const { connectors } = JSON.parse(result.content[0].text)
const connected = connectors.filter((c) => c.connected)
const disconnected = connectors.filter((c) => !c.connected)
console.log(`Connected services: ${connected.map((c) => c.name).join(', ')}`)
console.log(`Available but not connected: ${disconnected.map((c) => c.name).join(', ')}`)Fetch tool metadata
Call mcpgate_tool_metadata to get risk levels, categories, and action templates for every tool. Build a lookup map you can reference on each invocation.
const metaResult = await client.callTool({
name: 'mcpgate_tool_metadata',
arguments: {},
})
const { tools: toolMeta } = JSON.parse(metaResult.content[0].text)
// Build a lookup map: toolName → metadata
const metaByTool = Object.fromEntries(toolMeta.map((t) => [t.tool, t]))
// Example: separate tools by risk level
const readTools = toolMeta.filter((t) => t.riskLevel === 1)
const writeTools = toolMeta.filter((t) => t.riskLevel === 2)
const deleteTools = toolMeta.filter((t) => t.riskLevel === 3)
console.log(`Read tools (${readTools.length}): ${readTools.map((t) => t.title).join(', ')}`)
console.log(`Write tools (${writeTools.length})`)
console.log(`Delete tools (${deleteTools.length}) — require confirmation`)Read annotations from tools/list
The standard tools/list response includes MCP-spec annotations. Build a second lookup map so you can gate auto-approval and confirmation prompts.
// tools/list gives us annotations on each tool
const { tools } = await client.listTools()
// Build a lookup: toolName → annotations
const annotationsByTool = Object.fromEntries(
tools.map((t) => [t.name, t.annotations ?? {}])
)
function shouldAutoApprove(toolName: string): boolean {
const ann = annotationsByTool[toolName]
return ann?.readOnly === true
}
function requiresConfirmation(toolName: string): boolean {
const ann = annotationsByTool[toolName]
return ann?.destructive === true
}
// Usage:
console.log(shouldAutoApprove('gmail_read_email')) // true
console.log(requiresConfirmation('gmail_delete_email')) // true
console.log(shouldAutoApprove('gmail_send_email')) // falseInvoke tools with policy logic
Wrap tool invocations in a policy function that checks annotations before calling and logs activity entries using the action template afterwards.
async function invokeWithPolicy(
client: Client,
toolName: string,
args: Record<string, unknown>
): Promise<string> {
const ann = annotationsByTool[toolName] ?? {}
const meta = metaByTool[toolName]
// Destructive tools: require explicit user approval (implement your own prompt)
if (ann.destructive) {
const confirmed = await askUser(`Confirm: ${meta?.title ?? toolName}?`)
if (!confirmed) throw new Error('User cancelled')
}
const result = await client.callTool({ name: toolName, arguments: args })
const output = result.content[0].text
// Log to activity feed using action template
if (meta?.actionTemplate) {
const label = formatActivity(meta.actionTemplate, args)
logActivity({ tool: toolName, label, riskLevel: meta.riskLevel })
}
return output
}
function formatActivity(template: string, args: Record<string, unknown>): string {
return template.replace(/\{(\w+)\}/g, (_, key) =>
args[key] !== undefined ? String(args[key]) : `{${key}}`
)
}
// Example invocations:
const email = await invokeWithPolicy(client, 'gmail_read_email', {
messageId: '18d3f2a...',
})
// This will prompt the user before executing:
await invokeWithPolicy(client, 'gmail_delete_email', {
messageId: '18d3f2a...',
})Complete example#
The snippet below combines all five steps into a single runnable file.
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
async function main() {
// 1. Connect
const transport = new SSEClientTransport(
new URL('https://mcp.mcpgate.io/mcp'),
{ requestInit: { headers: { Authorization: `Bearer ${process.env.MCPGATE_API_KEY}` } } }
)
const client = new Client({ name: 'demo', version: '1.0.0' }, { capabilities: {} })
await client.connect(transport)
// 2. Discover connectors
const connResult = await client.callTool({ name: 'mcpgate_list_connectors', arguments: {} })
const { connectors } = JSON.parse(connResult.content[0].text)
console.log('Connected:', connectors.filter((c) => c.connected).map((c) => c.name))
// 3. Fetch metadata + annotations
const [metaResult, { tools }] = await Promise.all([
client.callTool({ name: 'mcpgate_tool_metadata', arguments: {} }),
client.listTools(),
])
const { tools: toolMeta } = JSON.parse(metaResult.content[0].text)
const metaByTool = Object.fromEntries(toolMeta.map((t) => [t.tool, t]))
const annotationsByTool = Object.fromEntries(tools.map((t) => [t.name, t.annotations ?? {}]))
// 4. Auto-approved read call
console.log('Annotations for gmail_read_email:', annotationsByTool['gmail_read_email'])
// → { title: 'Read Email', readOnly: true, idempotent: true, destructive: false, openWorldHint: true }
const readResult = await client.callTool({
name: 'gmail_read_email',
arguments: { messageId: '18d3f2a' },
})
console.log('Email:', readResult.content[0].text)
// 5. Activity feed entry
const meta = metaByTool['gmail_read_email']
const label = meta.actionTemplate.replace(/\{(\w+)\}/g, (_, k) => ({ messageId: '18d3f2a' })[k] ?? `{${k}}`)
console.log('Activity:', label) // → "Read email 18d3f2a"
await client.close()
}
main().catch(console.error)Multi-account tools
accountCount > 1 in the connector list), pass the desired account ID as the account parameter on any tool call targeting that connector. When omitted, MCPGate uses the account marked isDefault: true.What's next#
- Add Guardrail rules in the dashboard to enforce hard limits server-side, independent of your client policy logic.
- Check the Activity Log in the dashboard to verify that your tool calls are being recorded correctly.
- Review the Annotations and Metadata reference pages for the full field specifications.