Documentation Index
Fetch the complete documentation index at: https://docs.getcore.me/llms.txt
Use this file to discover all available pages before exploring further.
MCP Hub Integration
The MCP Hub allows integrations to expose tools that AI agents can use. This reference guide covers MCP configuration and tool implementation.
What is MCP Hub?
The MCP (Model Context Protocol) Hub is a centralized way for integrations to expose tools to AI agents. When an integration connects an MCP server, those tools become available to any AI agent using Core.
Example flows:
- Agent uses GitHub integration’s
create_issue tool to file a bug
- Agent uses Slack integration’s
send_message tool to notify a team
- Agent uses Linear integration’s
create_task tool to assign work
MCP Configuration
Configure MCP in your integration’s spec.json:
HTTP-based MCP
For services with existing HTTP MCP servers:
{
"mcp": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${config:access_token}",
"Content-Type": "application/json",
"X-Custom-Header": "${config:custom_value}"
}
}
}
Key points:
type: "http" - Specifies HTTP transport
url - The MCP server endpoint
headers - HTTP headers for authentication and configuration
${config:access_token} - Placeholder replaced with user’s OAuth token at runtime
- Any
${config:key} placeholder pulls from the user’s integration config
Stdio-based MCP
For command-line MCP servers:
{
"mcp": {
"type": "stdio",
"url": "https://integrations.heysol.ai/slack/mcp/slack-mcp-server",
"args": [],
"env": {
"SLACK_MCP_XOXP_TOKEN": "${config:access_token}",
"SLACK_MCP_ADD_MESSAGE_TOOL": true,
"SLACK_WORKSPACE_ID": "${config:team_id}"
}
}
}
Key points:
type: "stdio" - Specifies stdio transport (command-line)
url - Binary/executable URL or path
args - Command-line arguments array
env - Environment variables passed to the process
- Placeholders work in env vars too
Configuration Placeholders
The ${config:key} syntax pulls values from the user’s integration configuration:
Setup Phase
When user completes OAuth, you set the config:
// account-create.ts
export async function integrationCreate(data: any) {
const { oauthResponse } = data;
return [{
type: 'account',
data: {
accountId: user.id.toString(),
config: {
access_token: oauthResponse.access_token,
refresh_token: oauthResponse.refresh_token,
team_id: user.team_id,
custom_setting: 'value',
// This config is accessible in MCP as ${config:*}
mcp: {
tokens: {
access_token: oauthResponse.access_token
}
}
}
}
}];
}
Runtime Phase
Core replaces placeholders when connecting to MCP server:
// Before replacement (spec.json)
{
"mcp": {
"type": "http",
"url": "https://api.service.com/mcp/",
"headers": {
"Authorization": "Bearer ${config:access_token}",
"X-Team-ID": "${config:team_id}"
}
}
}
// After replacement (runtime)
{
"mcp": {
"type": "http",
"url": "https://api.service.com/mcp/",
"headers": {
"Authorization": "Bearer xoxp-actual-token-here",
"X-Team-ID": "T12345"
}
}
}
HTTP MCP Server Example (GitHub)
spec.json
{
"name": "GitHub extension",
"key": "github",
"description": "Manage GitHub repositories and issues",
"icon": "github",
"auth": {
"OAuth2": {
"token_url": "https://github.com/login/oauth/access_token",
"authorization_url": "https://github.com/login/oauth/authorize",
"scopes": ["repo", "user"]
}
},
"mcp": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${config:access_token}",
"Content-Type": "application/json"
}
}
}
When Core connects to GitHub’s MCP server, these tools become available:
github_create_issue - Create a new issue
github_search_code - Search code across repositories
github_create_pull_request - Create a new PR
github_list_commits - List commits for a repository
Agents can now call these tools:
// Agent calls this internally
await mcpClient.callTool('github_create_issue', {
owner: 'user',
repo: 'project',
title: 'Bug: Login fails',
body: 'Steps to reproduce...'
});
Stdio MCP Server Example (Slack)
spec.json
{
"name": "Slack extension",
"key": "slack",
"description": "Connect your workspace to Slack",
"icon": "slack",
"auth": {
"OAuth2": {
"token_url": "https://slack.com/api/oauth.v2.access",
"authorization_url": "https://slack.com/oauth/v2/authorize",
"scopes": ["chat:write", "channels:read", "users:read"]
}
},
"mcp": {
"type": "stdio",
"url": "https://integrations.heysol.ai/slack/mcp/slack-mcp-server",
"args": [],
"env": {
"SLACK_MCP_XOXP_TOKEN": "${config:access_token}",
"SLACK_MCP_ADD_MESSAGE_TOOL": true
}
}
}
The Slack MCP server exposes:
slack_send_message - Send message to channel
slack_list_channels - List workspace channels
slack_add_reaction - React to a message
slack_get_users - Get workspace users
Building Your Own MCP Server
If your service doesn’t have an existing MCP server, you can build one:
1. Use the MCP SDK
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = new Server(
{
name: 'my-service-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Define your tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'create_task',
description: 'Create a new task',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string' },
description: { type: 'string' },
},
required: ['title'],
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'create_task') {
const result = await createTask(args.title, args.description);
return {
content: [{ type: 'text', text: `Created task: ${result.id}` }],
};
}
throw new Error(`Unknown tool: ${name}`);
});
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
2. Authentication
Get tokens from environment variables:
const accessToken = process.env.MY_SERVICE_TOKEN;
if (!accessToken) {
throw new Error('MY_SERVICE_TOKEN not provided');
}
// Use token in API calls
const response = await fetch('https://api.myservice.com/tasks', {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
3. Expose in spec.json
{
"mcp": {
"type": "stdio",
"url": "/path/to/your-mcp-server",
"args": [],
"env": {
"MY_SERVICE_TOKEN": "${config:access_token}"
}
}
}
{
"name": "create_issue",
"description": "Create a new GitHub issue in a repository. Returns the issue number and URL.",
"inputSchema": {
"type": "object",
"properties": {
"owner": {
"type": "string",
"description": "Repository owner (username or organization)"
},
"repo": {
"type": "string",
"description": "Repository name"
},
"title": {
"type": "string",
"description": "Issue title (required, max 256 characters)"
},
"body": {
"type": "string",
"description": "Issue description in markdown format"
}
},
"required": ["owner", "repo", "title"]
}
}
2. Return Structured Data
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const result = await createIssue(args);
return {
content: [
{
type: 'text',
text: `Created issue #${result.number}: ${result.html_url}`
},
{
type: 'resource',
resource: {
uri: result.html_url,
mimeType: 'text/html',
text: JSON.stringify(result)
}
}
],
};
});
3. Error Handling
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const result = await performAction(args);
return {
content: [{ type: 'text', text: `Success: ${result}` }],
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Error: ${error.message}`
}],
isError: true,
};
}
});
4. Rate Limiting
let lastCallTime = 0;
const MIN_INTERVAL = 1000; // 1 second between calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const now = Date.now();
if (now - lastCallTime < MIN_INTERVAL) {
await new Promise(resolve =>
setTimeout(resolve, MIN_INTERVAL - (now - lastCallTime))
);
}
lastCallTime = Date.now();
// Proceed with tool call
});
1. Test with MCP Inspector
npx @modelcontextprotocol/inspector node your-mcp-server.js
2. Test with Claude Desktop
Add to Claude Desktop config:
{
"mcpServers": {
"your-service": {
"command": "node",
"args": ["/path/to/your-mcp-server.js"],
"env": {
"MY_SERVICE_TOKEN": "test-token"
}
}
}
}
// In your integration tests
const client = new Client({
name: 'test-client',
version: '1.0.0'
}, {
capabilities: {
tools: {}
}
});
await client.connect(transport);
const tools = await client.listTools();
console.log('Available tools:', tools);
const result = await client.callTool('create_task', {
title: 'Test task'
});
console.log('Tool result:', result);
Common Patterns
Generate tools based on user’s account:
server.setRequestHandler(ListToolsRequestSchema, async () => {
const user = await getUserFromToken(accessToken);
const projects = await getProjects(user.id);
// Generate tool for each project
const tools = projects.map(project => ({
name: `create_task_${project.id}`,
description: `Create task in ${project.name}`,
inputSchema: {
type: 'object',
properties: {
title: { type: 'string' }
}
}
}));
return { tools };
});
Composite Actions
Combine multiple API calls into one tool:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'create_issue_with_assignee') {
// 1. Create issue
const issue = await createIssue(args.title, args.body);
// 2. Assign user
await assignIssue(issue.number, args.assignee);
// 3. Add labels
await addLabels(issue.number, args.labels);
return {
content: [{
type: 'text',
text: `Created and configured issue #${issue.number}`
}],
};
}
});
Security Considerations
- Token Scoping - Only request OAuth scopes needed for MCP tools
- Input Validation - Validate all tool parameters before use
- Rate Limiting - Implement rate limits to prevent abuse
- Error Messages - Don’t expose sensitive data in error messages
- Token Storage - Tokens are securely encrypted in Core’s database
Next Steps