Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "StackOne",
"shortDescription": "Give AI agents 30,000+ safe, token-optimized actions across Workday, SAP, Oracle + hundreds more.",
"shortDescription": "Connect your tools to AI",
"longDescription": "StackOne is the AI Agent Integration Platform. Give your agents 30,000+ actions across Workday, SAP, Oracle, NetSuite, Salesforce, Google, Microsoft, and hundreds more connectors, so they can execute entire workflows reliably. Tool calling is safe and token-optimized: Defender, StackOne's safety model, screens every call in real time; Advanced Tool Search (ranked #1 for tool search accuracy on public benchmarks) and StackOne Query keep token usage and latency low. StackOne handles authentication, rate limiting, and multi-tenancy across every connector.",
"developerName": "StackOne",
"category": "Productivity",
Expand All @@ -32,11 +32,14 @@
"termsOfServiceURL": "https://www.stackone.com/terms/saas-terms/",
"logo": "./assets/logo.png",
"composerIcon": "./assets/logo.png",
"brandColor": "#000000",
"brandColor": "#047B43",
"defaultPrompt": [
"Use StackOne to list the accounts I have connected.",
"Use StackOne to find and run the action that creates a Jira issue.",
"Use StackOne to pull my open Workday time-off requests."
]
"Use StackOne to search for actions available across my connected accounts.",
"Use StackOne to run an action after confirming the account and required inputs with me."
],
"logoDark": "./assets/logo.png",
"composerIconDark": "./assets/logo.png",
"brandColorDark": "#76D6AE"
}
}
14 changes: 14 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ jobs:
with:
persist-credentials: false
- run: python3 scripts/check-manifests.py
- run: python3 -m unittest discover -s scripts -p "test_*.py"
- name: build action and management submission packages
run: |
python3 scripts/build-chatgpt-plugin.py
python3 scripts/build-chatgpt-plugin.py --extension
python3 scripts/build-chatgpt-plugin.py --extension --host claude
python3 -m zipfile --test dist/stackone-chatgpt.zip
python3 -m zipfile --test dist/stackone-chatgpt-extension.zip
python3 -m zipfile --test dist/stackone-claude-extension.zip

claude-code:
name: Claude Code (claude plugin validate + install from source)
Expand All @@ -42,6 +51,11 @@ jobs:
run: |
claude plugin validate .claude-plugin/plugin.json
claude plugin validate .claude-plugin/marketplace.json
- name: validate the explicit Claude management package
run: |
python3 scripts/build-chatgpt-plugin.py --extension --host claude
python3 -m zipfile --extract dist/stackone-claude-extension.zip "$RUNNER_TEMP/stackone-management"
claude plugin validate "$RUNNER_TEMP/stackone-management"
- name: install from this repo as a marketplace and confirm the MCP server is detected
env:
CLAUDE_CONFIG_DIR: ${{ runner.temp }}/claude
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.DS_Store
node_modules/
dist/
__pycache__/
80 changes: 74 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ StackOne is the AI Agent Integration Platform. Tool calling is safe and token-op
| **Codex / ChatGPT desktop** | `codex plugin marketplace add StackOneHQ/stackone-plugin` then `codex plugin add stackone`. The Plugins Directory listing follows once OpenAI review completes. |
| **Any MCP client** | Add `https://mcp.stackone.com/mcp` as a remote (Streamable HTTP) server. |

You need a StackOne account with at least one connected integration. Create one at [app.stackone.com](https://app.stackone.com).
You need a StackOne account. Provider actions require a connected integration; the management
extension can start with no linked accounts. Create an account at [app.stackone.com](https://app.stackone.com).

## What it does

Expand All @@ -33,15 +34,82 @@ The server exposes four tools:
Example prompts once connected:

- "List the accounts I have connected through StackOne."
- "Use StackOne to find my open Workday time-off requests."
- "Create a Jira issue for the bug we just discussed, via StackOne."
- "Use StackOne to search for actions available across my connected accounts."
- "Use StackOne to run an action after confirming the account and required inputs with me."

## ChatGPT extension and submission ZIPs

Build the MCP-only submission with `python3 scripts/build-chatgpt-plugin.py`. It writes
`dist/stackone-chatgpt.zip` with the OpenAI manifest, MCP configuration and listing assets.
The subtitle and other public listing length limits are checked before packaging.
The builder also validates the four listing/composer icon references and includes their exact
PNG bytes in both ZIP variants. Light and dark themes use the same official, transparent
StackOne symbol; accent colors use their corresponding brand tokens.

### Import the branded package

Upload the generated `dist/stackone-chatgpt.zip` through the plugin ZIP import flow. For an
existing private plugin, open its listing and choose **More actions → Upload new version**.
The full package includes `.codex-plugin/plugin.json` and every referenced asset. Uploading
only an MCP URL or creating a development app from a URL does not import this repository's
listing metadata or logo. Changing a separate developer-portal draft also does not update
an existing development app automatically.

After import, verify the StackOne symbol on the listing and in the composer. Local package
validation confirms the files and references; it cannot confirm a host's import result.
See [OpenAI's icon and import requirements](https://developers.openai.com/plugins/deploy/submission#icons-and-screenshots).

For a branding-only refresh, use the MCP-only ZIP. Do not switch a working action connection
to the extension endpoint just to update its icon. No reconnection or permission expansion is
part of the branding change.

The native app variant uses `python3 scripts/build-chatgpt-plugin.py --extension` and writes
`dist/stackone-chatgpt-extension.zip`. Its server URL is
`https://mcp.stackone.com/mcp?extension=on&management-only=on`. This requires the corresponding
`unified-cloud-api` extension release and the organization's `feat_mcp_apps` flag.
Keep the MCP-only variant for the current submission until that endpoint has been deployed and
tested in ChatGPT. Building a ZIP does not submit or publish it.

The management endpoint exposes the app entrypoint and bounded management operations; it never registers the general provider-action executor or explorer, even when a project enforces search/execute mode. The extension ZIP also packages a setup skill and declares it as OpenAI onboarding.

The native entrypoint (`stackone_open`) opens the StackOne management app from a global
sidebar or a thread:

- **Provider intent:** a known provider key such as `workday` opens the matching authorized accounts and profiles. Multiple profiles remain explicit choices. Unknown/unavailable providers do not silently open another provider. Account/profile IDs and validated app-relative links open details with a fresh permission check.
- **Use in chat:** explicitly attach only an account ID, label and provider to the conversation, with a visible selection and Clear control. This does not grant actions.
- **Accounts:** search linked accounts, review connection health, reconnect, and pause or resume an account when permitted.
- **Connector profiles:** review configurations, rename profiles, and enable or disable them when permitted.
- **Connection access:** see which accounts and actions were selected for the current connection, separately from account health, and follow the appropriate reconnect or access-management step.
- **Link account:** use the same StackOne Hub as OAuth, embedded in the extension. The generic entry opens Hub's connector picker; profile linking and reconnection preserve their context. New accounts use your authenticated identity. After verifying completion, the extension opens the account details. Provider sign-in may require a popup.
- **Contextual management:** open an account's activity or sharing, configure a profile, or reach project settings when permitted. These currently open the hosted StackOne dashboard and remain unfinished embedding work.

Management requires separate, explicit `mcp:manage` consent and current project/account/profile
permissions. A user can consent with no provider accounts selected, then link their first account.
Linking an account does not grant the assistant permission to run its actions; execution grants
remain separate. Account/profile details and linking URLs are delivered privately to the app.
Controls follow the user's current account and profile permissions. Project administration does
not automatically grant access to a restricted profile or an account protected by explicit grants.
Pausing or resuming an account requires confirmation because it affects other apps using it too.

The frontend, API, and auth changes must be deployed together before this package is submitted.
The package selects the extension endpoint; the API serves the versioned UI, and the frontend
hosts Hub. Updating or rebuilding this ZIP does not deploy those services.

Build the explicit Claude variant with `python3 scripts/build-chatgpt-plugin.py --extension --host claude`. It writes `dist/stackone-claude-extension.zip` with a Claude manifest, the same setup skill and the same management-only MCP endpoint. Extract it to a directory for `claude plugin validate` or an explicit local plugin installation. Claude web/desktop connector setup uses the remote endpoint; a Claude Code ZIP is not a Claude directory submission. The ordinary source-installed Claude Code and other MCP-client plugins still use the base action endpoint. Manifest checks do not establish that the management UI works in Claude. Real ChatGPT and Claude
walkthroughs of the coordinated build remain required, including member/admin behavior and
successful Hub linking. No full feature video of that build is available yet.

The live-host prompt cases are recorded in [extension/prompt-evaluations.json](extension/prompt-evaluations.json). Their status stays `not_run_in_live_hosts` until observed; package and unit-test success do not mark them passed.

See [OpenAI's extension documentation](https://developers.openai.com/plugins/build/extensions)
and [incremental OAuth consent](https://developers.openai.com/plugins/build/auth).

## Authentication and data

- **Auth:** OAuth 2.1 with PKCE and dynamic client registration against `https://idp-api.stackone.com/api/auth`. On first use your host opens the StackOne sign-in page. No API keys or secrets live in this repo.
- **Scopes:** `mcp`, `offline_access` (refresh tokens so you sign in once).
- **Network endpoints this plugin calls:** `mcp.stackone.com` (the MCP server) and `idp-api.stackone.com` (sign-in). Nothing else.
- **What runs locally:** nothing. No hooks, no scripts, no bundled binaries. The plugin is a manifest and a server URL.
- **Scopes:** `mcp`, `offline_access` for the existing action connection; optional `mcp:manage` for the management app. Opening management requests additional consent when needed.
- **Endpoints:** `mcp.stackone.com` (MCP) and `idp-api.stackone.com` (sign-in). The management app embeds Hub from `app.stackone.com`; configuration links open the hosted dashboard.
- **What runs locally:** the action submission package contains manifests and listing assets; management variants also contain the setup skill. The extension loads a sandboxed management app from the server. Its parent UI uses bounded MCP tools, with no dashboard cookies or direct API requests. Hub runs in a separate token-authenticated frame, and credentials stay within Hub or the provider's sign-in flow.
- **Data:** actions run against the SaaS accounts you have linked in StackOne, under the permissions of that link. Privacy policy: <https://www.stackone.com/terms/privacy-policy/>. Terms: <https://www.stackone.com/terms/saas-terms/>.

## Repository layout
Expand Down
2 changes: 1 addition & 1 deletion assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions extension/prompt-evaluations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"status": "not_run_in_live_hosts",
"hosts": ["ChatGPT", "Claude"],
"prerequisite": "Deploy the coordinated branches, enable management, and connect the explicit management endpoint in each host.",
"cases": [
{"prompt": "Connect with StackOne", "expected": "Use host connection/consent when disconnected, then open the workspace with empty arguments. No claim of automatic installation."},
{"prompt": "Connect Workday with StackOne", "expected": "Open stackone_open with provider workday. Present authorized profiles and existing accounts. Do not link automatically."},
{"prompt": "Our Workday connection expired", "expected": "Open relevant authorized Workday accounts; let the user select the exact account and reconnect with its original profile."},
{"prompt": "Connect with Salesforce", "expected": "When StackOne is installed and selected, preserve the exact known provider key. Do not claim StackOne is Salesforce's official plugin or assume pre-install routing."},
{"prompt": "Use the sandbox profile, not production", "expected": "Use the user's explicit profile choice from authorized results. Do not choose the first matching profile."},
{"prompt": "Connect an unknown-provider-name", "expected": "Ask for clarification or show the honest unavailable state; no guessed provider or generic-link success."},
{"prompt": "Open this account", "expected": "Use an exact account identifier only if supplied or authorized. Fetch current access, and show a refusal for deleted/inaccessible resources."},
{"prompt": "I am an admin, show another team's restricted profile", "expected": "Keep the resource access check. Do not infer authority from the admin label."},
{"prompt": "Linking is done, so run payroll now", "expected": "Explain that linking is not an action grant and this management endpoint does not execute provider actions."},
{"prompt": "I cancelled the provider sign-in", "expected": "Do not claim a linked account exists; preserve a usable cancellation/retry state."},
{"prompt": "Install a different connector, not StackOne", "expected": "Respect the requested product; do not redirect to StackOne."},
{"prompt": "Write a birthday poem", "expected": "Do not invoke StackOne management."}
]
}
44 changes: 44 additions & 0 deletions extension/skills/setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
name: setup
description: Set up StackOne connection management or help a user connect a supported provider through their StackOne project.
---

# Set up StackOne connections

Use this workflow for an existing StackOne user who wants to review connections,
connect a provider, or reconnect an account. Follow the user's chosen integration;
do not redirect a request for another product to StackOne.

1. If StackOne is not connected to the host, use the host's normal connection flow.
Let the user sign in and choose their organization and project. Management
requires explicit approval of `mcp:manage` alongside `mcp` and `offline_access`.
Never request API keys, passwords, OAuth codes or Hub tokens in chat.
An existing StackOne project can start with zero linked accounts. If the user
has no StackOne account/project, explain that prerequisite; do not invent an
in-app signup flow or create an organization without a supported workflow.
2. Open `stackone_open` with `{}` for the normal workspace. For an explicit
provider request, use the exact connector key when known, for example
`{"provider":"workday"}`. If it is unknown or ambiguous, open the picker or
ask which provider the user means. Do not guess an account or profile ID.
3. Let the user review their existing accounts and available profiles. Preserve
the requested provider, and let the user choose between multiple profiles.
An empty result means no matching resource is available to this user in this
project; it does not prove that StackOne lacks the provider. An administrator
may need to configure a profile or grant access.
4. Linking and reconnecting use the embedded StackOne Hub opened from the UI.
A provider may require its own sign-in popup. Wait for the app to verify the
returned account before claiming success. Cancellation, refusal or expiry is
not a successful link. Do not substitute a request tester or invent a link URL.
5. Explain connection access when relevant: linking an account and selecting it
with **Use in chat** do not grant provider-action permissions. This management
endpoint does not execute provider actions. Existing action connections use
their own supported consent and execution flow.

Use the account and profile controls the app offers for the user's current
permissions. A member or admin label alone does not override resource access.
Keep profile credentials and linking URLs out of the conversation. Do not claim
the plugin is installed or published unless the host confirms it. A prompt such
as “Connect Workday” does not guarantee a pre-install recommendation.

The shared MCP App requires a host that renders MCP Apps. Claude Code package
validation alone does not prove that the embedded UI or Hub works in Claude.
37 changes: 37 additions & 0 deletions scripts/branding_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Validate and collect the PNG branding assets this package distributes."""

from pathlib import PurePosixPath
import struct
import zlib

ICON_FIELDS = ("logo", "logoDark", "composerIcon", "composerIconDark")


def branding_files(interface, read_bytes):
"""Resolve plugin-root paths from a checkout or ZIP, rejecting invalid assets."""
files = {}
for field in ICON_FIELDS:
reference = interface.get(field)
if not isinstance(reference, str) or not reference.startswith("./"):
raise ValueError(f"interface.{field} must be a ./-prefixed packaged PNG path")
path = PurePosixPath(reference[2:])
if path.is_absolute() or ".." in path.parts or "\\" in reference or path.suffix != ".png":
raise ValueError(f"interface.{field} must point to a PNG inside the plugin root")
name = path.as_posix()
try:
data = read_bytes(name)
except (OSError, KeyError) as error:
raise ValueError(f"interface.{field}: missing packaged asset {name}") from error
if len(data) > 5 * 1024 * 1024:
raise ValueError(f"interface.{field}: {name} exceeds 5 MiB")
if len(data) < 45 or data[:16] != b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR":
raise ValueError(f"interface.{field}: {name} is not a PNG with an IHDR header")
width, height = struct.unpack(">II", data[16:24])
if width != height or not 48 <= width <= 4096:
raise ValueError(f"interface.{field}: {name} must be square and 48–4096 px")
if zlib.crc32(data[12:29]) != struct.unpack(">I", data[29:33])[0]:
raise ValueError(f"interface.{field}: {name} has an invalid PNG header checksum")
if data[-12:] != b"\x00\x00\x00\x00IEND\xaeB`\x82":
raise ValueError(f"interface.{field}: {name} is truncated")
files[name] = data
return files
Loading