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

# Quickstart

> Go from zero to your first policy-enforced tool call in 15 minutes.

In this guide, you'll set up Devctrl to govern an **AI agent's access to GitHub**. The agent needs to triage issues on a specific repo — but it shouldn't be able to create pull requests, delete branches, or access repos it's not assigned to.

You'll connect a GitHub MCP server, make tool calls, add policies, see them fail (on purpose), and learn how everything fits together.

## Prerequisites

* A Devctrl account at [console.devctrl.ai](https://console.devctrl.ai)
* A GitHub account with at least **two repositories** you own (one to allow, one to test denial)
* A [GitHub Personal Access Token (classic)](https://github.com/settings/tokens/new) with these scopes: `repo`, `read:org`, `read:user`
* [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for testing tool calls
* `curl` for creating task sessions

<Note>
  Throughout this guide, replace `YOUR_OWNER` with your GitHub username or organization, and `YOUR_REPO` with an actual repository name you own.
</Note>

***

## Part 1: Connect GitHub and make your first calls

<Steps>
  <Step title="Create a project">
    Log in to the [Devctrl Console](https://console.devctrl.ai) and create a new project.

    * **Name**: `engineering`
    * **MCP Enabled**: `Yes`
    * **Default policy action**: `ALLOW`

    We're starting with `ALLOW` so everything works out of the box. You'll lock it down with policies in Part 2.
  </Step>

  <Step title="Register the GitHub MCP server">
    Go to **Tools** in the top header and click **Register MCP server**. Select **GitHub** from the catalog.

    Enter a name (or keep the default) and paste your **GitHub Personal Access Token** in the token field.

    After saving, Devctrl connects to GitHub and discovers all available tools — `github__list_issues`, `github__issue_read`, `github__issue_write`, `github__create_pull_request`, `github__search_code`, and more.
  </Step>

  <Step title="Create an identity for the agent">
    Go to **Identities** and create a new identity.

    * **Name**: `triage-bot`

    Go to **Credentials** and click **Generate Credential**. Copy the secret — this is the agent's bearer token.

    <Tip>
      You can add labels like `team: devops` or `role: triage` from the identity's settings page. Labels are used in policy expressions.
    </Tip>

    <Warning>
      The credential is shown only once. Save it somewhere safe.
    </Warning>
  </Step>

  <Step title="Connect with MCP Inspector">
    Open [MCP Inspector](https://github.com/modelcontextprotocol/inspector) and connect to the gateway:

    * **Transport Type**: Streamable HTTP
    * **URL**: `https://gateway.devctrl.ai/mcp`
    * **Header**: `Authorization: Bearer YOUR_CREDENTIAL`

    Click **Connect**. The Inspector handles the MCP protocol handshake automatically.
  </Step>

  <Step title="List available tools">
    In the Inspector, go to the **Tools** tab. You should see all GitHub tools:

    * `github__list_issues`
    * `github__issue_read`
    * `github__issue_write`
    * `github__create_pull_request`
    * `github__search_code`
    * `github__get_file_contents`
    * ...
  </Step>

  <Step title="Make tool calls — everything is open">
    In the Inspector, call `github__list_issues` with arguments:

    ```json theme={null}
    { "owner": "YOUR_OWNER", "repo": "YOUR_REPO" }
    ```

    Works. Now try something the triage agent shouldn't be doing — call `github__create_pull_request`:

    ```json theme={null}
    {
      "owner": "YOUR_OWNER",
      "repo": "YOUR_REPO",
      "title": "Untested changes",
      "head": "some-branch",
      "base": "main"
    }
    ```

    That works too (or returns a GitHub error if the branch doesn't exist — but Devctrl **allowed** the call). Right now **every tool call is allowed** because the default policy action is `ALLOW` and there are no policies.

    Go to **Executions** in the console — you'll see both calls logged with decision `ALLOW`.
  </Step>
</Steps>

***

## Part 2: Lock it down with a policy

The triage agent should only read issues and repos — not create PRs or write anything. Let's add a policy.

<Steps>
  <Step title="Write a policy">
    Go to **Policies** and create a new rule.

    * **Name**: `read-only-github`
    * **Effect**: `Allow`
    * **Expression**:

    ```javascript theme={null}
    request.tool.name in [
      "github__list_issues",
      "github__issue_read",
      "github__search_issues",
      "github__get_file_contents",
      "github__list_commits",
      "github__search_code"
    ]
    ```

    * **Message on deny**: `This agent is restricted to read-only GitHub tools.`

    The **expression** evaluates to `true` only for read-only tools. If the agent tries `github__create_pull_request` or `github__issue_write`, the expression returns `false` and the call is denied. The **message on deny** is shown in the audit log and returned to the agent when a call is blocked.
  </Step>

  <Step title="Try creating a PR — still works">
    In the Inspector, call `github__create_pull_request` again:

    ```json theme={null}
    {
      "owner": "YOUR_OWNER",
      "repo": "YOUR_REPO",
      "title": "Should this be blocked?",
      "head": "some-branch",
      "base": "main"
    }
    ```

    **It still works.** Why? Because policies only take effect after you **publish a release**. Your policy is still a draft.

    <Note>
      This is intentional. The draft → publish workflow lets you write and review policies safely before they affect live traffic.
    </Note>
  </Step>

  <Step title="Publish a release">
    Back in the console, go to the **Releases** tab under **Policies** and click **Publish Release**. This creates a versioned snapshot of your current policies and makes it the active release.

    The gateway picks up the new policies within about a minute.
  </Step>

  <Step title="Try again — now it's enforced">
    Wait a moment, then call `github__create_pull_request` in the Inspector again.

    **Denied.** The policy blocked it because `github__create_pull_request` isn't in the allowed tools list.

    Now call `github__list_issues` with `{ "owner": "YOUR_OWNER", "repo": "YOUR_REPO" }` — it still works.

    Check **Executions** — you'll see the denied `github__create_pull_request` with the exact policy rule that blocked it, and the allowed `github__list_issues` call.
  </Step>
</Steps>

***

## Part 3: Scope the agent to one repo

The agent can now only read — but it can read from **any** repo. For issue triage, it should only access the repo it's assigned to. This is where **tasks** come in.

<Steps>
  <Step title="Define a task">
    Go to **Tasks** and create a new task.

    * **Name**: `triage-issues`
    * **Context schema**:

    ```json theme={null}
    {
      "type": "object",
      "properties": {
        "owner": {
          "type": "string",
          "description": "The GitHub owner (user or org)"
        },
        "repo": {
          "type": "string",
          "description": "The repository name"
        }
      },
      "required": ["owner", "repo"]
    }
    ```

    This requires the agent to specify which owner and repo it's triaging when it starts the task.
  </Step>

  <Step title="Create a second policy">
    Keep `read-only-github` as is. Now create a **second** policy that scopes access to the task's repo.

    * **Name**: `scope-to-assigned-repo`
    * **Effect**: `Allow`
    * **Expression**:

    ```javascript theme={null}
    request.tool.args.owner == task.context.owner
      && request.tool.args.repo == task.context.repo
    ```

    * **Message on deny**: `Tool call targets a repo outside the assigned task scope.`

    Your project now has **two policies**. Both must pass for a call to be allowed:

    1. `read-only-github` — is this a read-only tool?
    2. `scope-to-assigned-repo` — is the agent accessing the owner/repo from its task context?

    **Publish a new release** from the Releases tab to include both policies.
  </Step>

  <Step title="Create a task session">
    Assign the agent to triage a specific repo. Task sessions use a REST API, so you can use curl:

    ```bash theme={null}
    curl -s -X POST https://gateway.devctrl.ai/v1/tasks/sessions \
      -H "Authorization: Bearer YOUR_CREDENTIAL" \
      -H "Content-Type: application/json" \
      -d '{
        "taskId": "YOUR_TASK_ID",
        "context": {
          "owner": "YOUR_OWNER",
          "repo": "YOUR_REPO"
        },
        "ttl": 3600
      }' | jq
    ```

    Save the `taskToken` from the response. This token is scoped to this task and expires in 1 hour.
  </Step>

  <Step title="Test with the task token">
    In the Inspector, add the task token as an additional header:

    * **Header**: `X-Task-Token: YOUR_TASK_TOKEN`

    Now call `github__list_issues` with:

    ```json theme={null}
    { "owner": "YOUR_OWNER", "repo": "YOUR_REPO" }
    ```

    **Allowed.** The tool is read-only and the owner/repo matches the task context.
  </Step>

  <Step title="Access a different repo — denied">
    Still in the Inspector with the same task token, call `github__list_issues` with a **different** repo:

    ```json theme={null}
    { "owner": "YOUR_OWNER", "repo": "some-other-repo" }
    ```

    **Denied.** The task context says `repo: "YOUR_REPO"`, but the tool call targets `some-other-repo`. The `scope-to-assigned-repo` policy catches the mismatch.

    Check **Executions** to see both decisions with the full evaluation context — which policy passed, which failed, and why.
  </Step>
</Steps>

***

## What you've learned

1. **Without policies**, the agent can do anything — list issues, create PRs, access any repo
2. **Policies are drafts** until you publish a release — a common gotcha
3. **Multiple policies** work together — `read-only-github` restricts tools, `scope-to-assigned-repo` restricts data
4. **Task context** scopes access dynamically — the agent only sees the repo it's assigned to
5. **Every decision is logged** in the audit trail with full context

This is the core of Devctrl: **agents get exactly the access they need, for exactly the task they're performing, and every action is logged.**

## What's next

<CardGroup cols={2}>
  <Card title="Task-Based Access Control" icon="shield-check" href="/concepts/task-based-access-control">
    Understand TBAC and how it differs from traditional RBAC.
  </Card>

  <Card title="Connect an agent" icon="plug" href="/gateway/connect-an-agent">
    Point any MCP-compatible agent at the gateway.
  </Card>

  <Card title="CEL expressions" icon="code" href="/policies/cel-expressions">
    Full reference for writing policy expressions.
  </Card>

  <Card title="Policy examples" icon="book-open" href="/policies/examples">
    Real-world policy patterns you can copy and adapt.
  </Card>
</CardGroup>
