Skip to main content
Services live in _mcp/<service-name>/ in your workspace. The CLI scaffolds the directory structure, and you customize the configuration, tool definitions, and implementation.

Create a service

synthetiq service create slack
This scaffolds the service directory:
_mcp/slack/
├── src/
│   ├── Service.ts       # Service class with tool handlers
│   ├── tools.ts         # Tool definitions with input/output schemas
│   ├── config.ts        # Auth type, display name, settings
│   └── lib/
│       └── BaseService.ts
├── package.json
├── tsconfig.json
└── build.cjs

Configure the service

Edit src/config.ts to set the auth type and service metadata:
const SERVICE_CONFIG = {
  displayName: "Slack",
  description: "Send messages and manage channels in Slack",
  category: "Communication",
  authType: "oauth2",
  authorizationUrl: "https://slack.com/oauth/v2/authorize",
  tokenUrl: "https://slack.com/api/oauth.v2.access",
  scopes: "channels:read chat:write",
} as const satisfies Service;
For services using API keys instead of OAuth, set authType: "custom" and define the credential fields:
const SERVICE_CONFIG = {
  displayName: "Example API",
  description: "Example service with API key auth",
  category: "Developer Tools",
  authType: "custom",
} as const satisfies Service;

const CUSTOM_AUTH_SETTINGS = [
  {
    key: "apiKey",
    description: "Your API key",
    type: "password",
    required: true,
  },
] as const satisfies CustomAuthSetting[];

Define tools

Edit src/tools.ts to define the tools your service exposes. Each tool has a name, description, and input/output schemas:
export const TOOLS = {
  listChannels: {
    name: "listChannels",
    description: "List all public channels in the workspace",
    inputSchema: {
      type: "object",
      properties: {
        limit: { type: "number", description: "Max channels to return" },
      },
    },
    outputSchema: {
      type: "array",
      items: {
        type: "object",
        properties: {
          id: { type: "string" },
          name: { type: "string" },
        },
      },
    },
    mutation: false,
  },
  sendMessage: {
    name: "sendMessage",
    description: "Send a message to a Slack channel",
    inputSchema: {
      type: "object",
      properties: {
        channel: { type: "string", description: "Channel ID" },
        text: { type: "string", description: "Message text" },
      },
      required: ["channel", "text"],
    },
    outputSchema: {
      type: "object",
      properties: {
        ok: { type: "boolean" },
        ts: { type: "string" },
      },
    },
    mutation: true,
  },
} as const;
Set mutation: true for tools that modify data (writes, deletes, sends). This enables the framework to enforce access control policies on mutating operations.

Implement tool handlers

Edit src/Service.ts to implement the tool logic. The createClient method sets up an authenticated API client, and getToolHandler maps tool names to their implementations:
export default class MCPService extends BaseService<ServiceSettingsType, CustomAuthSettingsType> {
  getServiceSettings(): ServiceSettings {
    return SERVICE_SETTINGS;
  }

  createClient(context: Context) {
    return new WebClient(context.userCredentials.accessToken);
  }

  getTools(): ToolDefinition[] {
    return Object.values(TOOLS);
  }

  protected getToolHandler(toolName: string) {
    const handlers: Record<string, Function> = {
      listChannels: this.listChannels.bind(this),
      sendMessage: this.sendMessage.bind(this),
    };
    return handlers[toolName] || null;
  }

  private async listChannels(args: { limit?: number }, context: Context) {
    const client = this.createClient(context);
    const result = await client.conversations.list({ limit: args.limit });
    return result.channels;
  }

  private async sendMessage(args: { channel: string; text: string }, context: Context) {
    const client = this.createClient(context);
    return await client.chat.postMessage(args);
  }
}
The context parameter is provided by the framework at call time — it contains the authenticated user’s credentials, service settings, and request metadata. Your handler code never fetches or manages credentials directly.