Skip to main content
Glama
arucil
by arucil
README.md
# dsh-slack-mcp

A local, keyring-backed Slack MCP server for DSH / Codex. It keeps the pinned
upstream package (`@nrjdalal/slack-mcp-server`) unchanged and layers a small set
of conversation and workflow tools on top.

The Slack credential never lives in config, environment files, or the repo: a
small Go launcher reads a **user token** (`xoxp-…`) from the Secret Service and
passes it to the Node server through the child environment.

## Layout

```
launcher/          Go launcher: keyring → exec node server.mjs
server.mjs         MCP server assembly (upstream server + extensions)
conversations.mjs  conversation tools (info/members/invite/open/mark)
workflows.mjs      workflow tools (featured list / webhook start)
slack-api.mjs      Slack call wrapper with a public error allowlist
test/              node:test suites
```

## Tools

| Tool | Side effect | Notes |
| --- | --- | --- |
| `conversations_info` | none | channel/DM metadata |
| `conversations_members` | none | member IDs, paginated |
| `conversations_invite` | membership | explicit channel + user IDs only |
| `conversations_open` | DM creation | `prevent_creation=true` for lookup only |
| `workflows_featured_list` | none | workflow names / trigger IDs |
| `workflow_webhook_start` | external POST | one configured Workflow Builder webhook |

The upstream package contributes the ordinary Slack tools (`chat_post_message`,
`conversations_history`, `conversations_replies`, `conversations_list`,
`conversations_unreads`, `conversations_mark`, `search_messages`, reactions,
files). A deployment that fronts this server with the shared gateway filters
that catalog through its own allowlist (`shared/policy.mjs` in the gateway),
so enabling `conversations_mark` there is a policy change, not a code change.
Setting `SLACK_MCP_ALLOW_WRITE=false` (or `0`) omits every write tool.

## Requirements

- Node.js 20+
- Go 1.22+ (launcher only)
- A Slack app user token with: `channels:history groups:history im:history
  mpim:history channels:read groups:read im:read mpim:read search:read
  files:read channels:write groups:write im:write mpim:write chat:write
  reactions:write channels:write.invites groups:write.invites`
- `zod` and the two pinned packages from `package.json`

`conversations_mark` needs the same `*:write` scopes; it does not need
`stars:*` or `reminders:*`. It requires an explicit `ts` (fetch the latest via
`conversations_history` with `limit: 1` first).

## Install

```bash
npm ci
go build -o ~/.local/bin/dsh-slack-mcp ./launcher
```

Store the token once (stdin only, never an argument):

```bash
secret-tool store --label='Slack user token' \
  service donut-slack-task-dispatcher username slack-user-token
```

The launcher reads exactly that service/username pair, rejects anything that is
not an `xoxp-` token, and refuses to start if `server.mjs` is missing.

## MCP client configuration

```toml
[mcp_servers.slack-local]
command = "/home/you/.local/bin/dsh-slack-mcp"
```

The process speaks MCP over stdio and logs one line to stderr on startup.

## Tests

```bash
npm test
```

The suites use an in-memory transport and a fake Slack caller; no network call
is made and no token is required.

## Security notes

- The token is read from Secret Service at launch and passed only through the
  child environment; it is never written to disk by this project.
- Slack error payloads are reduced to a public allowlist. Raw SDK errors,
  headers, and tokens are never returned to the model.
- Writes are single-shot: a transport failure reports `outcome_unknown` instead
  of replaying the request. Read back state before retrying.
- `workflow_webhook_start` performs one POST to a preconfigured webhook URL
  stored in Secret Service under `service=codex-slack-mcp-workflows`. An
  accepted trigger is not a completed workflow.