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

# Custom apps and MCP

> Add custom credentials, app wrappers, and MCP servers.

Use custom credentials and MCP servers when the built-in [integration catalog](/integrations) does not cover the service you need, or when you're connecting to an internal API.

Most custom integrations start simple: define a credential, write an action that uses it, then attach that action to workflows or agents.

## Example requests

Ask your coding agent which API or MCP server you want to reach. It can define the credential, write the 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 API key credential

Use `defineCredential()` for static secrets. The `key` is the credential slug users will set in the web app or CLI, and `fields` describes the secret shape.

```ts src/credentials/acme.ts theme={null}
import { defineCredential } from "@keystrokehq/keystroke/credentials";
import { z } from "zod";

export const acme = defineCredential({
  key: "acme",
  fields: {
    apiKey: z.string(),
  },
});
```

Then declare it on an action:

```ts src/actions/create-acme-ticket.ts theme={null}
import { defineAction } from "@keystrokehq/keystroke/action";
import { z } from "zod";
import { acme } from "../credentials/acme";

export const createAcmeTicket = defineAction({
  slug: "create-acme-ticket",
  input: z.object({ title: z.string(), body: z.string() }),
  output: z.object({ id: z.string() }),
  credentials: [acme] as const,
  async run(input, credentials) {
    return createTicket({
      apiKey: credentials.acme.apiKey,
      title: input.title,
      body: input.body,
    });
  },
});
```

Connect the credential:

```bash theme={null}
keystroke credentials set acme --set apiKey=@env:ACME_API_KEY --scope org
```

See [using credentials in code](/learn/credentials/use-credentials) for scope resolution.

## Wrap a custom app

If you have several actions for the same service, you can define an app wrapper and create actions from it. This keeps the credential definition shared across all actions.

```ts src/apps/acme.ts theme={null}
import { defineApp } from "@keystrokehq/keystroke/app";
import { z } from "zod";

export const acme = defineApp({
  slug: "acme",
  auth: "api_key",
  credential: {
    apiKey: z.string(),
  },
});
```

```ts src/actions/create-acme-ticket.ts theme={null}
import { z } from "zod";
import { acme } from "../apps/acme";

export const createAcmeTicket = acme.action({
  slug: "create-acme-ticket",
  input: z.object({ title: z.string(), body: z.string() }),
  output: z.object({ id: z.string() }),
  async run(input, credentials) {
    return createTicket({
      apiKey: credentials.acme.apiKey,
      title: input.title,
      body: input.body,
    });
  },
});
```

This is mainly useful for package-style integrations or when you want a family of actions to share one app credential.

## Connect an MCP server

Agents can connect to any MCP server you define in code. MCP tools are attached through the agent's `tools` array, just like actions.

```ts src/agents/researcher.ts theme={null}
import { defineAgent, defineMcp } from "@keystrokehq/keystroke/agent";

const deepwiki = defineMcp({
  key: "deepwiki",
  transport: {
    type: "http",
    url: "https://mcp.deepwiki.com/mcp",
  },
});

export default defineAgent({
  slug: "researcher",
  systemPrompt: "Use DeepWiki tools for questions about GitHub repositories.",
  model: "anthropic/claude-sonnet-4.6",
  tools: [deepwiki],
});
```

If the MCP server needs authentication, declare credentials on the MCP definition and map them into headers:

```ts theme={null}
const internalMcp = defineMcp({
  key: "internal",
  transport: { type: "http", url: "https://mcp.example.com" },
  credentials: [acme] as const,
  auth: (credentials) => ({
    headers: { Authorization: `Bearer ${credentials.acme.apiKey}` },
  }),
});
```

You cannot register arbitrary MCP servers from the web app yet. Custom MCP servers are authored in code and deployed with the project.

## 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:

* `defineCredential()` plus actions for custom APIs.
* `defineApp()` for a reusable app/action wrapper.
* `defineMcp()` for custom MCP servers attached to agents.

<Note>
  Hosted MCP management and managed service settings in the web app are not general-availability features today. Treat code-defined MCP servers as the supported custom MCP path.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Using credentials in code" href="/learn/credentials/use-credentials">
    Understand credential declaration, scopes, and defaults.
  </Card>

  <Card title="Actions" href="/learn/actions/overview">
    Build the action that consumes your custom credential.
  </Card>

  <Card title="Build agents" href="/learn/agents/build-agents">
    Attach actions and MCP servers as agent tools.
  </Card>

  <Card title="Connect and manage apps" href="/learn/credentials/connect-credentials">
    Store the API key or OAuth credential your code will resolve.
  </Card>
</CardGroup>
