{"package":"@executor.sh/slack","files":[{"path":"README.md","content":"# Slack\n\nChannels, messages, threads, users, files, reactions, pins, reminders, user groups, search and the rest of the Slack Web API (171 tools, 108 of them mutations; every mutation asks for approval).\n\n**Interface:** OpenAPI. Source is Slack's official Swagger 2.0 description (version 1.7.0), `https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/slack_web_openapi_v2.json`, published by the `slackapi` GitHub organisation on `master` (not pinned to a commit). The document is patched at load time and is not Slack's verbatim file: 19 schemas whose `items` is an array (invalid Swagger 2 tuple form) are replaced with a plain object schema, which otherwise causes core methods such as `chat.postMessage` and `conversations.list` to be skipped. The patches are in `openapi.ts`.\n\n**Approvals:** Slack declares many side-effecting methods as GET (`views.open`, `views.publish`, `apps.uninstall`, `auth.revoke`, `dialog.open`, `files.remote.share`, `workflows.*` and others). These are forced to mutations with `kinds` so they ask for approval. Some read-only POST methods are also treated as mutations and so ask for approval; this is safe but noisy. The `oauth.access`, `oauth.token` and `oauth.v2.access` methods are removed because they take `client_secret` as a query parameter. Slack's official remote MCP server (`https://mcp.slack.com/mcp`) was probed but needs a directory-published or internal Slack app, so it is not usable for arbitrary users.\n\n**Auth**\n\n- Token: a bot token (`xoxb-`) or user token (`xoxp-`) from your Slack app's \"OAuth & Permissions\" page at https://api.slack.com/apps. User tokens are needed for methods such as `search.messages`.\n- OAuth: create a Slack app at https://api.slack.com/apps, add the redirect URI shown on Executor's connect page under \"OAuth & Permissions\", and enter its client ID and secret. The flow requests bot scopes (comma separated) and yields a bot token; reduce the scopes to what the app needs. Every requested scope was checked against its docs.slack.dev scope page as a bot scope; `mpim:write` is user-token only and is not requested.\n\n**Known gaps**\n\n- No operations are skipped after the patches (171 of the 174 spec operations are exposed; the 3 removed are the oauth.* methods above).\n- OAuth yields a bot token only; user-token-only methods (search, some admin methods) need the token method.\n- File uploads via the legacy multipart `files.upload` are not supported.\n- Icon is Slack's hash mark from the Slack website (`a.slack-edge.com`), as Simple Icons has no Slack entry.\n"},{"path":"icon.ts","content":"import type { AppIcon } from \"apps\";\n\n// Generated by scripts/icon.ts from the vendor's logo.\nexport const icons: readonly AppIcon[] = [\n  {\n    \"src\": \"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTI3IiBoZWlnaHQ9IjEyNyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNMjcuMiA4MGMwIDcuMy01LjkgMTMuMi0xMy4yIDEzLjJDNi43IDkzLjIuOCA4Ny4zLjggODBjMC03LjMgNS45LTEzLjIgMTMuMi0xMy4yaDEzLjJWODB6bTYuNiAwYzAtNy4zIDUuOS0xMy4yIDEzLjItMTMuMiA3LjMgMCAxMy4yIDUuOSAxMy4yIDEzLjJ2MzNjMCA3LjMtNS45IDEzLjItMTMuMiAxMy4yLTcuMyAwLTEzLjItNS45LTEzLjItMTMuMlY4MHoiIGZpbGw9IiNFMDFFNUEiLz4KICA8cGF0aCBkPSJNNDcgMjdjLTcuMyAwLTEzLjItNS45LTEzLjItMTMuMkMzMy44IDYuNSAzOS43LjYgNDcgLjZjNy4zIDAgMTMuMiA1LjkgMTMuMiAxMy4yVjI3SDQ3em0wIDYuN2M3LjMgMCAxMy4yIDUuOSAxMy4yIDEzLjIgMCA3LjMtNS45IDEzLjItMTMuMiAxMy4ySDEzLjlDNi42IDYwLjEuNyA1NC4yLjcgNDYuOWMwLTcuMyA1LjktMTMuMiAxMy4yLTEzLjJINDd6IiBmaWxsPSIjMzZDNUYwIi8+CiAgPHBhdGggZD0iTTk5LjkgNDYuOWMwLTcuMyA1LjktMTMuMiAxMy4yLTEzLjIgNy4zIDAgMTMuMiA1LjkgMTMuMiAxMy4yIDAgNy4zLTUuOSAxMy4yLTEzLjIgMTMuMkg5OS45VjQ2Ljl6bS02LjYgMGMwIDcuMy01LjkgMTMuMi0xMy4yIDEzLjItNy4zIDAtMTMuMi01LjktMTMuMi0xMy4yVjEzLjhDNjYuOSA2LjUgNzIuOC42IDgwLjEuNmM3LjMgMCAxMy4yIDUuOSAxMy4yIDEzLjJ2MzMuMXoiIGZpbGw9IiMyRUI2N0QiLz4KICA8cGF0aCBkPSJNODAuMSA5OS44YzcuMyAwIDEzLjIgNS45IDEzLjIgMTMuMiAwIDcuMy01LjkgMTMuMi0xMy4yIDEzLjItNy4zIDAtMTMuMi01LjktMTMuMi0xMy4yVjk5LjhoMTMuMnptMC02LjZjLTcuMyAwLTEzLjItNS45LTEzLjItMTMuMiAwLTcuMyA1LjktMTMuMiAxMy4yLTEzLjJoMzMuMWM3LjMgMCAxMy4yIDUuOSAxMy4yIDEzLjIgMCA3LjMtNS45IDEzLjItMTMuMiAxMy4ySDgwLjF6IiBmaWxsPSIjRUNCMjJFIi8+Cjwvc3ZnPgo=\",\n    \"mimeType\": \"image/svg+xml\"\n  }\n];\n"},{"path":"index.ts","content":"import {\n  accountRouter,\n  boolean,\n  decodeJson,\n  defineApp,\n  defineProvider,\n  object,\n  oauth2,\n  router,\n  secrets,\n  string,\n  withApprovals,\n} from \"apps\";\nimport { always } from \"apps/operations/approval\";\nimport { liveOpenapiRouter } from \"apps/openapi\";\nimport { icons } from \"./icon.js\";\nimport { options } from \"./openapi.js\";\n\n// Slack answers HTTP 200 with {ok:false,error} for bad tokens, so decode `ok` first and only\n// then rely on the fields that are present on success.\nconst Auth = object({\n  ok: boolean(),\n  error: string().optional(),\n  user_id: string().optional(),\n  user: string().optional(),\n  url: string().optional(),\n});\n\nconst slack = defineProvider({\n  name: \"Slack\",\n  hosts: [\"slack.com\"],\n  auth: {\n    token: secrets({\n      label: \"Bot or user token (xoxb-/xoxp-)\",\n      fields: object({ token: string({ minLength: 1 }) }),\n    }),\n    // Slack offers no client registration: the connect page asks for a Slack app's client.\n    oauth: oauth2({\n      authorizationUrl: \"https://slack.com/oauth/v2/authorize\",\n      tokenUrl: \"https://slack.com/api/oauth.v2.access\",\n      tokenEndpointAuthMethod: \"client_secret_post\",\n      scopeSeparator: \",\",\n      scopes: [\n        \"channels:read\",\n        \"channels:history\",\n        \"channels:join\",\n        \"channels:manage\",\n        \"groups:read\",\n        \"groups:history\",\n        \"im:read\",\n        \"im:history\",\n        \"im:write\",\n        \"mpim:read\",\n        \"mpim:history\",\n        \"chat:write\",\n        \"reactions:read\",\n        \"reactions:write\",\n        \"pins:read\",\n        \"pins:write\",\n        \"files:read\",\n        \"files:write\",\n        \"users:read\",\n        \"users:read.email\",\n        \"users.profile:read\",\n        \"usergroups:read\",\n        \"team:read\",\n        \"emoji:read\",\n        \"bookmarks:read\",\n        \"bookmarks:write\",\n        \"reminders:read\",\n        \"reminders:write\",\n        \"canvases:read\",\n        \"canvases:write\",\n      ],\n    }),\n  },\n  async health({ account, fetch, signal }) {\n    const token = account.method === \"oauth\" ? account.fields.access_token : account.fields.token;\n    const response = await fetch(\"https://slack.com/api/auth.test\", {\n      method: \"POST\",\n      signal,\n      headers: { Authorization: `Bearer ${token}` },\n    });\n    const me = await decodeJson(response, Auth);\n    if (!me.ok || me.user_id === undefined) {\n      throw new Error(`Slack auth.test failed: ${me.error ?? \"unknown_error\"}`);\n    }\n    return { accountInfo: { externalId: me.user_id, username: me.user, profileUrl: me.url } };\n  },\n});\n\nexport default defineApp(\n  { user: { accounts: { slack: slack.many() } }, icons },\n  async ({ user, cache, fetch, signal }) => ({\n    tools: router(\n      await accountRouter(\n        user?.accounts.slack ?? [],\n        (account) =>\n          withApprovals(liveOpenapiRouter({ ...options, cache, fetch, signal, account }), (tool) =>\n            tool.kind === \"mutation\" ? always() : undefined,\n          ),\n        { signal },\n      ),\n      {\n        title: \"Slack\",\n        description:\n          \"Channels, messages, threads, users, files, reactions, search and the rest of the Slack Web API.\",\n        icons,\n      },\n    ),\n  }),\n);\n"},{"path":"openapi.ts","content":"import type { OpenapiToolNamesOptions } from \"apps/openapi\";\n\n// Slack's official Swagger 2.0 description, version 1.7.0 (slackapi/slack-api-specs). Every\n// operation names the `slackAuth` scheme; Slack accepts any token type as a bearer header.\n//\n// The vendor document is not used verbatim. It contains schemas whose `items` is an array (the\n// invalid Swagger 2 tuple form), which the compiler rejects and which would drop core methods such\n// as chat.postMessage and conversations.list. Each such location is replaced with a plain object\n// schema (these are response schemas only). The oauth.* methods are removed because they are\n// credential exchanges that take client_secret as a query parameter.\nconst itemsPatches = [\n  \"/definitions/objs_channel/properties/latest/items\",\n  \"/definitions/objs_conversation/items\",\n  \"/definitions/objs_message/properties/bot_id/items\",\n  \"/definitions/objs_resources/properties/excluded_ids/items/items\",\n  \"/definitions/objs_resources/properties/ids/items/items\",\n  \"/definitions/objs_response_metadata/items\",\n  \"/definitions/objs_subteam/properties/auto_type/items\",\n  \"/definitions/objs_subteam/properties/deleted_by/items\",\n  \"/definitions/objs_team/properties/discoverable/items\",\n  \"/definitions/objs_team_profile_field/properties/options/items\",\n  \"/definitions/objs_user/items\",\n  \"/paths/~1conversations.history/get/responses/200/schema/properties/channel_actions_ts/items\",\n  \"/paths/~1conversations.open/post/responses/200/schema/properties/channel/items\",\n  \"/paths/~1conversations.replies/get/responses/200/schema/properties/messages/items/items\",\n  \"/paths/~1pins.list/get/responses/200/schema/items\",\n  \"/paths/~1reactions.get/get/responses/200/schema/items\",\n  \"/paths/~1reactions.list/get/responses/200/schema/properties/items/items/items\",\n  \"/paths/~1stars.list/get/responses/200/schema/properties/items/items/items\",\n  \"/paths/~1users.identity/get/responses/200/schema/items\"\n];\n\n// Slack declares these side-effecting methods as GET, so the framework would classify them as queries.\nconst getMutations = [\"apps_uninstall\",\"auth_revoke\",\"apps_permissions_request\",\"apps_permissions_users_request\",\"dialog_open\",\"files_remote_share\",\"migration_exchange\",\"views_open\",\"views_publish\",\"views_push\",\"views_update\",\"workflows_stepCompleted\",\"workflows_stepFailed\",\"workflows_updateStep\"];\n\nexport const options = {\n  source: {\n    url: \"https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/slack_web_openapi_v2.json\",\n  },\n  allowedOrigin: \"https://slack.com\",\n  securitySchemes: { slackAuth: { type: \"http\", scheme: \"bearer\" } },\n  methods: {\n    token: [{ scheme: \"slackAuth\", field: \"token\", part: \"value\", prefix: \"\" }],\n    oauth: [{ scheme: \"slackAuth\", field: \"access_token\", part: \"value\", prefix: \"\" }],\n  },\n  oauth: [],\n  patches: [\n    ...itemsPatches.map((path) => ({ op: \"replace\" as const, path, value: { type: \"object\" } })),\n    ...[\"oauth.access\",\"oauth.token\",\"oauth.v2.access\"].map((method) => ({\n      op: \"remove\" as const,\n      path: `/paths/~1${method}`,\n    })),\n  ],\n  kinds: Object.fromEntries(getMutations.map((id) => [id, \"mutation\" as const])),\n} satisfies OpenapiToolNamesOptions;\n"},{"path":"package.json","content":"{\n  \"name\": \"@executor.sh/slack\",\n  \"description\": \"Channels, messages, threads, users, files, reactions, search and the rest of the Slack Web API.\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"executor\": {\n    \"title\": \"Slack\"\n  },\n  \"dependencies\": {\n    \"apps\": \"0.0.1-beta.100\"\n  }\n}\n"}]}