# Forge Connector

> >

- **Type:** Skill
- **Install:** `agentstack add skill-atlassian-forge-skills-forge-connector`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [atlassian](https://agentstack.voostack.com/s/atlassian)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [atlassian](https://github.com/atlassian)
- **Source:** https://github.com/atlassian/forge-skills/tree/main/skills/forge-connector
- **Website:** https://developer.atlassian.com

## Install

```sh
agentstack add skill-atlassian-forge-skills-forge-connector
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Forge Connector

Builds a `graph:connector` Forge app that ingests external data into Atlassian's Teamwork Graph so it appears in **Rovo Search** and **Rovo Chat**.

## Critical Rules

1. **Must install in Jira** — Apps using Teamwork Graph modules must be installed on a Jira site. Confluence-only installs will not work.
2. **Never ask for credentials in chat** — Direct users to run `forge login` in their own terminal.
3. **Always run the scaffold script yourself** — Do not only give manual instructions; run `scripts/scaffold_connector.py` to generate the boilerplate.
4. **Always ask the user for their Atlassian site URL** when install is needed — never discover or guess it.
5. **Atlassian deletes data on disconnect** — When `action = 'DELETED'`, the app only needs to clean up local state; Atlassian removes the Teamwork Graph data automatically.
6. **Handler arguments are passed directly** — Forge passes the request object as the first argument to handlers, NOT nested under `event.payload`. Config values are at `request.configProperties`, NOT `event.payload.config`. This is the most common source of `TypeError: Cannot destructure property of undefined` errors.
7. **Use `@forge/kvs` for storage** — Import `kvs` from `@forge/kvs`. Do NOT use `@forge/storage` — its `storage` export is `undefined` at runtime in connector functions.
8. **Use `graph` named export from `@forge/teamwork-graph`** — The correct import is `const { graph } = require('@forge/teamwork-graph')`. Call `graph.setObjects({ objects, connectionId })`. Do NOT import `setObjects` as a named export directly.
9. **`validateConnectionHandler` must return `{ success, message }`** — Do NOT throw an Error. Return `{ success: false, message: '...' }` to reject, `{ success: true }` to accept.
10. **`function` declarations belong under `modules`** — In `manifest.yml`, `function:` is a key under `modules:`, not a top-level key. Placing it at the top level causes a lint error.
11. **`formConfiguration` uses `form` array with `type: header`** — Do NOT use `fields:` or `beforeYouBegin:`. The correct format uses `form: [{ key, type: header, title, description, properties: [...] }]`.
12. **Scopes are `read/write/delete:object:jira`** — Use `read:object:jira`, `write:object:jira`, `delete:object:jira`. The scopes `read:graph:teamwork` and `write:graph:teamwork` are invalid and will fail `forge lint`.
13. **Set `ATL_FORGE_ATTRIBUTION_SKILL_NAME=forge-connector` on `forge` commands run for this skill** — prefix `forge` invocations with this env var: ones you run in the shell (e.g. `forge lint`, `forge logs`, `forge deploy`) **and the interactive `forge create` command you hand the user as a fallback**. The bundled scripts set it automatically; other commands shown in this skill omit it for brevity — add it when you run them. The only exclusions are `forge login` and `forge tunnel` (user-run auth / live-dev commands).

## MCP Prerequisites

| MCP Server    | Purpose                                           |
| ------------- | ------------------------------------------------- |
| **Forge MCP** | Manifest syntax, module config, deployment guides |
| **ADS MCP**   | Atlaskit components (only if adding Custom UI)    |

---

## Agent Workflow — Complete Steps 0–7 in Order

### Step 0: Prerequisites

Check Node.js (`node -v`, requires 22+), Forge CLI (`forge --version`), and login (`forge whoami`). Install missing tools:

```bash
npm install -g @forge/cli
```

Tell the user to run `forge login` in their terminal if not authenticated.

### Step 1: Discover Developer Spaces

> **Note:** `forge developer-spaces list` does NOT exist in Forge CLI 12.x. You cannot list developer spaces non-interactively.

`forge create` requires an interactive TTY to select a developer space. Ask the user to run it themselves:

```
Tell the user:
  cd 
  ATL_FORGE_ATTRIBUTION_SKILL_NAME=forge-connector forge create --template blank 

  When prompted, select a Developer Space and let it complete.
  Come back when done.
```

The `--dev-space-id` flag in the scaffold script is optional and can be omitted — the script has been updated to skip it when not provided.

### Step 1.5: Discover Data & Map to Object Types

**Do this before scaffolding.** Ask the user the following questions to determine the correct Teamwork Graph object type(s). Do not assume or default to `atlassian:document`.

#### Questions to ask the user

1. **What external system or tool are you connecting?**
   e.g. Google Drive, ServiceNow, Salesforce, GitHub, Confluence, Slack, Figma, Zendesk

2. **What kind of content do you want to make searchable in Rovo?**
   Prompt with examples to help them identify it:
   - Files, pages, wiki articles, reports, PDFs → likely `atlassian:document`
   - Tasks, tickets, issues, bugs, stories → likely `atlassian:work-item`
   - Chat messages, emails, comments → likely `atlassian:message` or `atlassian:comment`
   - Projects, workspaces, boards → likely `atlassian:project`
   - Code repositories → likely `atlassian:repository`
   - Pull requests / merge requests → likely `atlassian:pull-request`
   - Git commits → likely `atlassian:commit`
   - Design files (Figma, Sketch) → likely `atlassian:design`
   - Video recordings → likely `atlassian:video`
   - Calendar events, meetings → likely `atlassian:calendar-event`
   - Threads, channels → likely `atlassian:conversation`
   - Customer accounts or organisations → likely `atlassian:customer-organization`
   - Team spaces or org units → likely `atlassian:space`

3. **Is the content a single type or a mix?**
   If mixed (e.g. a project management tool with tasks *and* documents), plan to ingest each as its own object type. The scaffold supports one primary type — you can add more `objectTypes` entries in `manifest.yml` later.

4. **Does the admin need to supply credentials (API key, URL, OAuth token) to connect?**
   Yes → use `--has-form-config` in the scaffold command.
   No (data comes entirely from within Atlassian) → omit the flag.

5. **How often does the source data change?**
   Frequently (hourly) → plan a `scheduledTrigger` with `interval: hour`.
   Daily or less → `interval: day`.
   Static / one-off → no scheduled trigger needed.

6. **Who should be able to see the ingested content in Rovo Search?**
   This determines the `permissions.accessControls` on each object. Ask:
   - "Is all this content publicly accessible, or does the source system restrict who can see what?"
   - "Do you want Rovo Search results to respect those source-system permissions?"

   Map the answer to the correct principal model:

   | Source system access model | `accessControls` to use |
   |---|---|
   | Publicly accessible, no restrictions | `principals: [{ type: 'EVERYONE' }]` |
   | Specific named users have access | `principals: [{ type: 'user', id: '' }]` — one entry per user |
   | Team or group based (e.g. Confluence space, Google Workspace group) | `principals: [{ type: 'group', id: '' }]` — one entry per group |
   | Private / owner only | single `user` principal with the owner's Atlassian account ID |
   | Mixed (per-object ACLs from the source) | fetch ACLs per item during ingestion and map each to a `user` or `group` principal |

   **Do NOT default to `EVERYONE`** unless the user explicitly confirms content is publicly accessible. Using `EVERYONE` on restricted content leaks data to users who shouldn't see it in Rovo Search.

   Record the chosen permission model before proceeding to Step 2. Reference it when writing the `setObjects` call in Step 3.

#### Mapping decision

Based on the answers, select the best-fit type from the Object Types table below. Only fall back to `atlassian:document` if the content genuinely has no better match (e.g. arbitrary file attachments). For types marked ❌ in the "Indexed in Rovo" column (`atlassian:build`, `atlassian:deployment`, `atlassian:test`), warn the user that those objects will not appear in Rovo Search or Rovo Chat.

Record the chosen object type(s) and permission model before proceeding to Step 2.

### Step 2: Scaffold the Connector App

Run from the **skill directory** (the directory containing this SKILL.md). Replace `` with the type determined in Step 1.5. `--dev-space-id` is optional:

```bash
python3 -m scripts.scaffold_connector \
  --name  \
  --connector-name "" \
  --object-type  \
  --directory 
```

Add `--dev-space-id ` only if you have the ID from a previous step.

**Object type** — use the type chosen in Step 1.5. Do NOT default to `atlassian:document` without first completing the discovery questions above.

**Form config flag** — add `--has-form-config` if the admin must provide API credentials or connection details (determined in Step 1.5 question 4). Omit it for apps that operate entirely within Atlassian (no external credentials needed).

> **If scaffold fails because `forge create` needs a TTY:** The scaffold script will print a manual fallback command. Have the user run `forge create` interactively, then continue from Step 3 — the scaffold script only needs to write `manifest.yml` and `src/index.js` after the directory exists.

### Step 3: Customize the Generated Code

After scaffolding (or after the user runs `forge create` interactively):

```bash
cd 
npm install
```

The blank template generates `src/index.js` (JavaScript, not TypeScript). Edit it to add your API calls. The scaffold generates working handler skeletons; fill in your business logic.

#### Key files to edit

| File           | What to change                                                          |
| -------------- | ----------------------------------------------------------------------- |
| `src/index.js` | `fetchExternalData()` — replace with your API calls                     |
| `manifest.yml` | Add `permissions.external.fetch.backend` URLs for any external APIs     |
| `package.json` | Add `@forge/api`, `@forge/kvs`, `@forge/teamwork-graph` as dependencies |

#### setObjects — ingest data into Teamwork Graph

Use the `graph` named export — do NOT destructure `setObjects` directly:

```javascript
const { graph } = require('@forge/teamwork-graph');

const result = await graph.setObjects({
  connectionId,          // required — the connectionId from the handler request
  objects: [
    {
      schemaVersion: '1.0',
      id: 'unique-id-from-source',      // unique per connectionId
      updateSequenceNumber: 1,
      displayName: 'My Document Title',
      url: 'https://source-system.example.com/doc/123',
      createdAt: '2024-01-15T10:00:00Z',        // ISO 8601
      lastUpdatedAt: '2024-01-20T14:30:00Z',
      // Use the permission model chosen in Step 1.5 question 6.
      // EVERYONE only if content is confirmed publicly accessible.
      // For user-restricted content: { type: 'user', id: '' }
      // For group-restricted content: { type: 'group', id: '' }
      permissions: [{
        accessControls: [{
          principals: [{ type: 'EVERYONE' }],
        }],
      }],
      'atlassian:document': {
        type: {
          category: 'DOCUMENT',   // see Document Categories table below
          mimeType: 'application/vnd.google-apps.document',
        },
        content: {
          mimeType: 'application/vnd.google-apps.document',
          text: 'document title or snippet for search indexing',
        },
      },
    },
  ],
});

if (!result.success) {
  console.error('setObjects error:', result.error);
}
```

- Max **100 objects per call** — batch large datasets with a loop
- `id` must be unique per `connectionId`
- `connectionId` is required in every `graph.setObjects()` call

#### Document Categories (for `atlassian:document.type.category`)

| MIME type | Category |
|---|---|
| `application/vnd.google-apps.document` | `DOCUMENT` |
| `application/vnd.google-apps.spreadsheet` | `SPREADSHEET` |
| `application/vnd.google-apps.presentation` | `PRESENTATION` |
| `application/vnd.google-apps.folder` | `FOLDER` |
| `application/pdf` | `PDF` |
| `image/*` | `IMAGE` |
| `video/*` | `VIDEO` |
| `audio/*` | `AUDIO` |
| Other | `OTHER` |

#### getObjectByExternalId — look up a single object

```javascript
const { graph } = require('@forge/teamwork-graph');

const data = await graph.getObjectByExternalId({
  externalId: 'unique-id-from-source',
  objectType: 'atlassian:document',
  connectionId,
});
if (data.success) console.log(data.object);
```

### Step 4: Deploy and Install

**You MUST run the deploy script** — do not only give the user manual `forge deploy` commands.

The deploy script lives in the **forge-app-builder** skill, not in this skill. Derive its directory from the path of this SKILL.md: go up two levels (`skills/forge-connector/` → `skills/`) then into `forge-app-builder/`. Run all commands below from that directory.

```bash
# Derive forge-app-builder skill dir from this SKILL.md's path:
# e.g. if this file is at /path/to/skills/forge-connector/SKILL.md
# then the deploy script dir is: /path/to/skills/forge-app-builder/

# If you have the site URL:
python3 -m scripts.deploy_forge_app \
  --app-dir  \
  --site  \
  --product jira

# If you don't have the site URL yet, deploy first then ask:
python3 -m scripts.deploy_forge_app \
  --app-dir  \
  --product jira \
  --deploy-only
# Ask: "What is your Atlassian site URL (e.g. yourcompany.atlassian.net)?"
python3 -m scripts.deploy_forge_app \
  --app-dir  \
  --site  \
  --product jira \
  --skip-deps
```

### Step 5: Connect via Atlassian Administration

After deployment, tell the user to:

1. Go to **Atlassian Administration** → **Apps** → **[site]** → **Connected apps**
2. Find the app → **View app details** → **Connections** tab
3. Click **Connect** under the connector
4. Fill in any configuration fields (if `formConfiguration` was defined)
5. Click **Connect** — this triggers `onConnectionChange` with `action: CREATED` and starts data ingestion

### Step 6: Monitor with forge tunnel

Use `forge tunnel` during development to stream live logs directly to your terminal as the connector functions execute. This is the fastest way to catch errors in `onConnectionChangeHandler`, `validateConnectionHandler`, and `setObjects` calls without waiting for `forge logs`.

Tell the user to run this in their own terminal (it requires an interactive session):

```bash
cd 
forge tunnel
```

With the tunnel active, any invocation of the connector functions (e.g. clicking "Connect" in Atlassian Admin, or triggering a scheduled re-ingestion) will stream output immediately. Look for:

- `[connector] Fetched N items` — confirms `fetchExternalData()` ran
- `[connector] Batch 1: N accepted, 0 rejected` — confirms `setObjects` succeeded
- Any uncaught errors or thrown exceptions from `validateConnectionHandler`

If the tunnel is not running, use `forge logs` instead to inspect past invocations:

```bash
# Most recent 50 log lines from development environment
forge logs -e development --limit 50

# Production logs for a specific site
forge logs -e production --site  --limit 50
```

**Tunnel vs logs — when to use which:**

| Situation | Use |
|---|---|
| Actively developing / testing the connection flow | `forge tunnel` — live streaming |
| Debugging a past invocation or production issue | `forge logs` |
| Connector function timed out before tunnel caught it | `forge logs` with `--limit 100` |

> **Note:** `forge tunnel` must be run by the user in an interactive terminal — do not attempt to run it via the agent.

### Step 7: End-to-End Verification (optional)

Before running any checks, ask the user:

> "Would you like to run end-to-end verification checks before deploying to production? This confirms the connection, ingestion, Rovo Search visibility, and permission boundaries are all working correctly."

If the user says **no** or wants to skip, move on — do not run or describe the checks.
If the user says **yes**, work through every check below in order.

#### Check 1 — Connection established

In **Atlassian Administration → Apps → Connected apps**, the conne

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [atlassian](https://github.com/atlassian)
- **Source:** [atlassian/forge-skills](https://github.com/atlassian/forge-skills)
- **License:** Apache-2.0
- **Homepage:** https://developer.atlassian.com

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-atlassian-forge-skills-forge-connector
- Seller: https://agentstack.voostack.com/s/atlassian
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
