---
title: One command to make GitHub Copilot read your Jira, and see what every answer costs
description: pilotkit is an open-source CLI that connects a GitHub Copilot seat to Jira and Confluence in one command, with a read-only MCP server and the API token in the OS keychain, plus a local desk that shows the AI credits each answer costs.
date: 2026-10-12
tags: GitHub Copilot, MCP, Jira, Confluence
project: https://github.com/brbousnguar/pilotkit
---

GitHub Copilot can read your Jira issues and Confluence pages. It just needs an MCP server, an Atlassian API token and two config files in two formats first. [pilotkit](https://github.com/brbousnguar/pilotkit) does all of that with one command, `pilotkit setup`, and adds a small local desk that shows what each answer cost in AI credits.

It's open source and on npm: `npm install -g pilotkit`. It started as a desk I run at work. pilotkit is the version colleagues can install on their own PCs without me sitting next to them.

## Copilot can't see your tickets yet

A Copilot seat comes with chat, agent mode in VS Code and a CLI. None of them know your Atlassian site exists.

Doing it by hand means installing an MCP server, creating an API token and usually pasting it into a JSON file, then registering the server twice: `copilot mcp add` for the Copilot CLI, and a `mcp.json` for VS Code in a different format. Easy to do once for yourself, tedious to walk a whole team through.

<figure class="fd-fig" aria-labelledby="pk1-cap" data-desc="Two ways to connect Copilot to Jira and Confluence. By hand it's four jobs: install an MCP server, create an Atlassian API token and paste it into a JSON file, register the server in the Copilot CLI with copilot mcp add, and write a mcp.json for VS Code in another format. With pilotkit it's one command, pilotkit setup: the token goes to the OS keychain and never to a file, and the server is registered in both clients while every other server is kept.">
<div class="mp-two-row">
<div class="mp-two-card">
<p class="mp-two-who">By hand</p>
<p class="mp-two-name">Four jobs</p>
<p class="mp-two-line">Install an MCP server</p>
<p class="mp-two-line">Create an API token, paste it into a JSON file</p>
<p class="mp-two-line"><code>copilot mcp add</code> for the Copilot CLI</p>
<p class="mp-two-line">A <code>mcp.json</code> for VS Code, in another format</p>
</div>
<p class="mp-two-vs">vs</p>
<div class="mp-two-card mp-two-peter">
<p class="mp-two-who">pilotkit</p>
<p class="mp-two-name">One command</p>
<p class="mp-two-line"><code>pilotkit setup</code></p>
<p class="mp-two-line">The token goes to the OS keychain, never to a file</p>
<p class="mp-two-line">Registered in both clients, other servers kept</p>
</div>
</div>
<figcaption id="pk1-cap">Same result, two routes. The blue card is the one you can hand to a colleague.</figcaption>
</figure>

## What one command does

Setup walks through three checks and only saves what passed:

1. **Copilot.** It finds the Copilot CLI, offers to install it, and signs you in with `copilot login`.
2. **Atlassian.** It asks for the site, your email and an API token, then proves them with one call to `/rest/api/3/myself`. A wrong token gets two more tries.
3. **Clients.** It registers the server with the Copilot CLI and in every VS Code user `mcp.json` it finds, keeping the other servers and a backup of the file.

The token never lands in a config file. On a Mac it goes into the Keychain through `security -i`, which reads its command from stdin. Passing the token as an argument would put it in the process list, where other programs running as you can read it with `ps`. On Windows it's a file only your Windows account can decrypt (DPAPI), and on Linux it's the Secret Service.

One bug I caught before it bit anyone: with Homebrew, Node's own path points into a versioned `Cellar` folder. Register that and the command breaks at the next `brew upgrade`. Setup now registers the `opt/` path, which survives upgrades.

<figure class="fd-fig mp-trio-fig" aria-labelledby="pk2-cap" data-desc="What pilotkit setup does, in three checks. One, Copilot: it finds the Copilot CLI, offers to install it and signs in with copilot login. Two, Atlassian: it asks for the site, the email and an API token and proves them with one call to /rest/api/3/myself; a wrong token gets two more tries. Three, clients: it registers the server with the Copilot CLI and in every VS Code user mcp.json, keeping the other servers and a backup. The token is stored in the macOS Keychain, in a DPAPI-protected file on Windows, or in the Linux Secret Service, never in a config file.">
<ol class="mp-trio">
<li class="mp-trio-step">
<p class="mp-trio-n"><b>1</b>Copilot</p>
<p class="mp-trio-what">Find or install the CLI, then <code>copilot login</code></p>
</li>
<li class="mp-trio-step">
<p class="mp-trio-n"><b>2</b>Atlassian</p>
<p class="mp-trio-what">Site, email and token, proved against <code>/myself</code></p>
</li>
<li class="mp-trio-step">
<p class="mp-trio-n"><b>3</b>Clients</p>
<p class="mp-trio-what">Copilot CLI and VS Code, other servers kept</p>
</li>
</ol>
<p class="mp-strip"><b>The token</b><span>macOS Keychain, Windows DPAPI or the Linux Secret Service. Never a config file.</span></p>
<figcaption id="pk2-cap">Each step checks its answer before the next one starts, so a bad token never gets saved.</figcaption>
</figure>

## Seven tools, all read-only

The MCP server has seven tools: `whoami`, `jira_my_issues`, `jira_search`, `jira_get_issue`, `jira_list_projects`, `confluence_search` and `confluence_get_page`. They only send `GET` requests, with your own Atlassian account, so Copilot sees what you'd see in a browser and nothing more. It can't comment, move a ticket or edit a page. Want a comment? Ask for a draft and paste it yourself.

They also return short text, on purpose. Jira stores descriptions as ADF, a JSON tree, and Confluence stores pages as XHTML. Both are noisy. Every character a tool returns goes back to the model with every later call in the chat, so pilotkit turns them into compact Markdown and cuts a description at 6,000 characters and a page at 8,000 by default.

<figure class="fd-fig" aria-labelledby="pk3-cap" data-desc="The seven pilotkit tools, all read-only: whoami, jira_my_issues, jira_search, jira_get_issue, jira_list_projects, confluence_search and confluence_get_page. There is no tool that writes: no comment, no transition, no page edit. Their output is short on purpose: Jira's ADF and Confluence's XHTML become compact Markdown, with a description cut at 6,000 characters and a page at 8,000 by default, because every character a tool returns is sent again with each later model call.">
<div class="mw-ro">
<div class="mw-tools">
<p class="mw-tools-h">Reads</p>
<ul class="mw-chips"><li>whoami</li><li>jira_my_issues</li><li>jira_search</li><li>jira_get_issue</li><li>jira_list_projects</li><li>confluence_search</li><li>confluence_get_page</li></ul>
<p class="mw-tools-h">Writes</p>
<ul class="mw-chips mw-chips-none"><li>comment</li><li>transition</li><li>edit a page</li></ul>
</div>
</div>
<p class="mp-strip"><b>Short output</b><span>ADF and XHTML become compact Markdown, cut at 6,000 and 8,000 characters.</span></p>
<figcaption id="pk3-cap">Seven tools that read and none that write. The struck-out ones don't exist.</figcaption>
</figure>

## The fun part: what an answer costs

Copilot bills in AI credits against a monthly allowance, and the chat window doesn't tell you what a question just spent. So `pilotkit start` opens a desk on `127.0.0.1:8717` that runs Copilot through the official [Copilot SDK](https://github.com/github/copilot-sdk) and counts.

After every model call the SDK sends an `assistant.usage` event. Its `copilotUsage.totalNanoAiu` is the cost in nano AI units, and 1,000,000,000 of them make one credit. The desk stores that per call in SQLite and puts the total under each answer, with a table of the model calls and tool calls behind it.

My first real question, about my open Jira issues, with Claude Haiku 5.5: **2 model calls, 1 tool call, 0.12 credits**. The first call asked for `jira_my_issues`, the second wrote the answer. A follow-up in the same chat cost 0.03.

When a call comes back without a cost, the desk estimates it from the model's token prices and marks it with ≈. Each chat also gets a cap, 60 credits by default, which Copilot enforces itself through `sessionLimits.maxAiCredits`. It won't take less than 30.

<figure class="fd-fig mp-trio-fig" aria-labelledby="pk4-cap" data-desc="What one answer cost, from a real run. One: a model call reads the question and asks for jira_my_issues. Two: the tool call reads Jira with your own account. Three: a second model call writes the answer from the result. With Claude Haiku 5.5 that first question took 2 model calls and 1 tool call and cost 0.12 credits; a follow-up in the same chat cost 0.03. Each chat has a cap, 60 credits by default. The cost comes from the assistant.usage event the Copilot SDK sends after every model call: one credit is 1,000,000,000 nano AI units.">
<ol class="mp-trio">
<li class="mp-trio-step">
<p class="mp-trio-n"><b>1</b>Model call</p>
<p class="mp-trio-what">Reads the question, asks for <code>jira_my_issues</code></p>
</li>
<li class="mp-trio-step">
<p class="mp-trio-n"><b>2</b>Tool call</p>
<p class="mp-trio-what">Reads Jira with your own account</p>
</li>
<li class="mp-trio-step">
<p class="mp-trio-n"><b>3</b>Model call</p>
<p class="mp-trio-what">Writes the answer from the result</p>
</li>
</ol>
<div class="mp-stats">
<p><b>0.12</b><span>credits for that first question, Claude Haiku 5.5</span></p>
<p><b>0.03</b><span>for a follow-up in the same chat</span></p>
<p><b>60</b><span>credits, the default cap per chat</span></p>
</div>
<p class="mp-strip"><b>1 credit</b><span>1,000,000,000 nano AI units, read from <code>assistant.usage</code> after every model call.</span></p>
<figcaption id="pk4-cap">One answer, taken apart. The numbers are from my first real question on the desk.</figcaption>
</figure>

## Locked to this machine

A desk that spends credits shouldn't answer anyone else. It listens on `127.0.0.1` only, and that isn't enough on its own: a web page you visit can still send requests to localhost. So it checks two more things.

The `Host` header must be `127.0.0.1` or `localhost`. A DNS name pointed at 127.0.0.1 gets a `421`, which stops DNS rebinding. And every write needs an `X-Pilotkit: 1` header. A form or an image on another site can't send one, so it gets a `403` and can't spend your credits.

Inside, the desk's Copilot sessions only see the pilotkit tools. A permission handler refuses the shell, files, URLs and other MCP servers, with a reason the model can read.

<figure class="fd-fig" aria-labelledby="pk5-cap" data-desc="Who can use the desk. You, in your own browser on 127.0.0.1 port 8717, reach it. A form or image on another site sends no X-Pilotkit header, so its write gets a 403. A DNS name pointed at 127.0.0.1 arrives with the wrong Host header and gets a 421. And inside the desk, a model that asks for a shell is refused by the permission handler, with the reason. Desk chats only see pilotkit's seven tools.">
<ol class="ts-list ts-who">
<li class="ts-yes"><p class="ts-who-n">You, in your browser</p><p class="ts-hop">127.0.0.1:8717</p><p class="ts-end">the desk</p></li>
<li class="ts-no"><p class="ts-who-n">A form on another site</p><p class="ts-hop">no X-Pilotkit ✗</p><p class="ts-end">403</p></li>
<li class="ts-no"><p class="ts-who-n">A DNS name pointed here</p><p class="ts-hop">wrong Host ✗</p><p class="ts-end">421</p></li>
<li class="ts-no"><p class="ts-who-n">The model, asking for a shell</p><p class="ts-hop">permission handler ✗</p><p class="ts-end">refused, with the reason</p></li>
</ol>
<p class="mp-strip"><b>Tools in a desk chat</b><span>Only pilotkit's seven.</span></p>
<figcaption id="pk5-cap">Four ways to knock, one that gets in. The 421 and the 403 are what the desk answered when I tried them.</figcaption>
</figure>

## Your logo, not mine

I made it for colleagues, so it should look like their company's tool, not my side project. But a company logo is a trademark, and it doesn't belong in a public repo.

So the package stays neutral and a brand pack carries the look: a folder with a `brand.json` and a logo, shared inside the company on a drive or an intranet URL.

```bash
pilotkit setup --brand ./brand-acme
```

It sets the name, logo, accent colour and welcome line on the desk and in setup, and can pre-fill the Atlassian site. The repo ships one example, for a company that doesn't exist: Acme.

<figure class="fd-fig" aria-labelledby="pk6-cap" data-desc="Where the branding lives. The public pilotkit package is neutral: the pilotkit name, logo and colour, plus one example pack for a made-up company called Acme. A company's own pack stays inside the company, on a drive or an intranet URL: its name, logo and accent colour, a welcome line and help links, and the Atlassian site pre-filled. It is applied with pilotkit setup --brand followed by the folder or URL.">
<div class="mp-two-row">
<div class="mp-two-card">
<p class="mp-two-who">Public package</p>
<p class="mp-two-name">pilotkit desk</p>
<p class="mp-two-line">Neutral name, logo and colour</p>
<p class="mp-two-line">One example pack, for a made-up Acme</p>
</div>
<p class="mp-two-vs">+</p>
<div class="mp-two-card mp-two-peter">
<p class="mp-two-who">Your company's pack</p>
<p class="mp-two-name">Your desk</p>
<p class="mp-two-line">Your name, logo and accent colour</p>
<p class="mp-two-line">A welcome line, help links, the Atlassian site pre-filled</p>
<p class="mp-two-line">Shared on a drive or an intranet URL</p>
</div>
</div>
<p class="mp-strip"><b>Applied with</b><span><code>pilotkit setup --brand &lt;folder or URL&gt;</code></span></p>
<figcaption id="pk6-cap">The marks stay with the company that owns them. The public repo only knows Acme.</figcaption>
</figure>

## Try it

```bash
npm install -g pilotkit
pilotkit setup
pilotkit start
```

You need Node 22.13 or newer, a Copilot plan and an Atlassian Cloud site. Once it's set up, the same tools also work in VS Code's agent mode and in the Copilot CLI. `pilotkit doctor` checks every part and says what to run when something's off.

<figure class="fd-fig mp-loop" aria-labelledby="pk7-cap" data-desc="Getting started in three steps. One: install it from npm with npm install -g pilotkit; it needs Node 22.13 or newer. Two: run pilotkit setup to sign in to Copilot and connect Atlassian. Three: run pilotkit start and ask on the desk, or use the same tools in VS Code's agent mode or the Copilot CLI. Tested on macOS against a real Jira and Confluence Cloud site; Windows and Linux are not verified yet.">
<div class="mp-loop-in">
<div class="flow" role="group" aria-label="Getting started: install pilotkit from npm, run pilotkit setup, then pilotkit start">
  <div class="flow-node">
    <p class="flow-label">1 · npm</p>
    <h3>Install</h3>
    <ul>
      <li>npm install -g pilotkit</li>
      <li>Node 22.13 or newer</li>
    </ul>
  </div>
  <div class="flow-link" aria-hidden="true"><span>→</span></div>
  <div class="flow-node">
    <p class="flow-label">2 · Setup</p>
    <h3>Connect</h3>
    <ul>
      <li>pilotkit setup</li>
      <li>Copilot sign-in, Atlassian token</li>
    </ul>
  </div>
  <div class="flow-link" aria-hidden="true"><span>→</span></div>
  <div class="flow-node">
    <p class="flow-label">3 · Ask</p>
    <h3>Desk</h3>
    <ul>
      <li>pilotkit start</li>
      <li>Or VS Code, or the Copilot CLI</li>
    </ul>
  </div>
</div>
</div>
<p class="mp-strip"><b>Tested</b><span>macOS, against a real Jira and Confluence Cloud site. Not yet: Windows and Linux.</span></p>
<figcaption id="pk7-cap">Three commands to a first answer.</figcaption>
</figure>

It's v0.1 and Apache-2.0, on [npm](https://www.npmjs.com/package/pilotkit). I've tested it on macOS against a real Jira and Confluence Cloud site: all seven tools, the desk on desktop and phone, and the guards above. Windows and Linux aren't verified yet, so if you run it there, tell me what breaks. Issues are open on [GitHub](https://github.com/brbousnguar/pilotkit).

*GitHub Copilot is a GitHub trademark; Atlassian, Jira and Confluence are Atlassian's. pilotkit is an independent project.*
