> ## Documentation Index
> Fetch the complete documentation index at: https://synthetiq.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Defining a service

> Create, configure, and build a service integration

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

```bash theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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.
