Skip to main content
Use a custom app when the built-in catalog does not cover the service you need, or when you’re connecting to an internal API. Search the live catalog first:
Before writing code, read the service’s current API or MCP documentation. Prefer an authoritative llms.txt, OpenAPI or GraphQL schema, or API reference over inferred endpoints and payloads.

Two layers: catalog + code

A credentialed custom integration has two parts. Do not skip either when the user should connect the app in the product UI. Code-only defineApp does not register an org catalog app. Without apps create, the service will not show up in the Connect dialog, and keystroke connect <slug> will not work for it. Actions that need no credentials stay as plain defineAction in src/actions/ — no app required.

End-to-end happy path

  1. Search the catalog — confirm the app is missing (keystroke apps search).
  2. Register the appkeystroke apps create so it is connectable in the org.
  3. Connect — user enters the secret in the web Apps flow (keystroke connect <slug> or the MCP connect_app link), or keystroke credentials create for a static key you already have in the shell.
  4. Author in codedefineApp + app.action(...) (or keystroke apps sync <slug> to scaffold the app module from the platform template).
  5. Deploy — ship actions/agents/workflows that use those tools.
When a public OpenAPI, GraphQL, or MCP URL is available, prefer URL-based create so auth and fields are detected:
Remove --preview after reviewing the assembled request. Use only the flag matching the source. For a manual API-key app:
--description defaults to --name when omitted. At least one --field is required. Optional --logo is a public image URL shown in the Apps catalog and Connect UI. In the web dashboard, click the logo placeholder on create to upload an image or paste a URL. Custom org apps are registered as {organization}/{name} (for example wells/demo-echo), not a bare demo-echo slug. Use that full id for connect, credentials create, apps sync, and defineApp({ slug }). After create, prefer:
That writes src/apps/<name>/app.ts with the correct slug and credential fields. keystroke apps get <org>/<name> returns { kind: "custom", app: { … } } with the credential template. Built-in catalog apps return { kind: "catalog", app: { package, … } } instead. apps actions list means different things by app kind: For a custom MCP, the list is an inspect step: every tool the server advertises, whether or not you have wrapped it in app.action(...). Smoke-test a remote tool with apps execute, then author the TypeScript wrappers you need. This split will improve later; today treat MCP list as remote discovery, not a project action inventory. See the CLI apps reference.

Smoke-testing custom app actions

keystroke apps execute runs built-in catalog toolkit actions and custom MCP remote tools. It cannot invoke defineApp(...).action(...) code in your project for non-MCP custom apps. To smoke-test a custom (non-MCP) app action before wiring it into larger agents/workflows:
  1. Author the action with defineApp(...).action(...) (or apps sync + fill in the handler).
  2. Connect the app credential (keystroke connect <org>/<name>).
  3. Add a thin workflow that calls that one action.
  4. Deploy, then run it for real:
For MCP custom apps, connect, list tools, then smoke-test a remote tool with the same command family as catalog apps:
Then wrap the tools you need in app.action(...) and attach them (or the whole app) to an agent. To remove a custom org app (and all of its credentials), use:
Official catalog apps cannot be deleted. In the web dashboard, open Connect an app, select the custom app, and click Delete.

Example requests

Ask your coding agent which API or MCP server you want to reach. It should register a connectable app when needed, define the app in code, write actions, and attach them.
“Connect to our internal billing API and add actions to look up and refund an invoice.”
“Attach the DeepWiki MCP server to the research agent so it can answer questions about any GitHub repo.”

Define a custom app (required for credentialed integrations)

For any new HTTP integration that needs a secret or connected account, create an app wrapper and define actions from it. Credentials belong on the app; actions are created with app.action().
src/apps/acme/app.ts
src/actions/create-acme-ticket.ts
The app slug must match the catalog/credential key from apps create (the full {org}/{name} id). Official catalog apps can still support self-hosted instances when their credential shape includes a base URL or similar field.
Anti-pattern: do not create src/credentials/foo.ts plus a lone defineAction for a new API. Create src/apps/foo.ts with defineApp and app.action(...) instead.
After the app is registered and connected:
See using credentials in code for scope resolution.

When standalone defineCredential is appropriate

Reserve ad-hoc defineCredential() for rare cases — for example a shared secret used by unrelated actions that are not one service, or an MCP auth helper. It is not the default path for a new integration.

Connect an MCP server

Register the MCP as an org app, connect auth, then inspect live tools, smoke-test with apps execute, author app.action wrappers, and attach the app (or individual actions) to the agent. Credentials stay on the platform; code mirrors the app slug. You cannot list MCP tools until the app is connected (OAuth or API key). NO_AUTH servers are the only exception.
apps actions list on a custom MCP uses the connected credential to call the remote tools/list. That output is every tool the server exposes right now — it is not the same as listing official catalog actions, and those tools may not exist as TypeScript app.action(...) functions yet. Use apps execute to smoke-test a remote tool, then wrap what you need. If nothing is connected yet, the CLI tells you to connect first.
Attach tools either way:
Assign the app credential to the agent, then deploy. Do not import defineMcp from @keystrokehq/keystroke/agent (it is not a supported authoring API). OAuth MCP apps expose accessToken for use as Authorization: Bearer against the remote MCP URL.

Current limitations

keystroke.config.ts has an integrations option, but custom HTTP integration mounting is not a shipped user-facing extension point yet. Today, use:
  • defineApp() + app.action() for credentialed custom HTTP APIs (default).
  • keystroke apps create when the app must appear in Connect / the org catalog.
  • keystroke apps delete <org>/<name> to remove a custom org catalog app (and its credentials).
  • Plain defineAction() for project actions that need no credentials.
  • keystroke apps create --mcp + connect + apps actions list + apps execute for custom MCP servers.
Search Keystroke docs (keystroke docs search) before inventing MCP integration patterns. Live tool lists fill gaps when vendor docs are thin.

Next steps

Using credentials in code

Understand credential declaration, scopes, and defaults.

Actions

Build actions that consume your app credential.

Build agents

Attach actions as agent tools.

Connect and manage apps

Store the API key or OAuth credential your code will resolve.