Skip to content
Merged
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
65 changes: 65 additions & 0 deletions packages/apps/connector-github/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# GitHub

Search and read GitHub issues and pull requests from your flows. The app
connects to GitHub's hosted MCP server (`https://api.githubcopilot.com/mcp/`)
in read-only mode and exposes two of its read tools as connector actions.
Nothing is written to GitHub.

## What it adds

- **A connector, `github_mcp`.** Its actions are `search_issues` (GitHub's
issue and pull request search syntax) and `issue_read` (one issue's details).
Any other tool the server offers stays out of your flows and agents.
- **Two flows that use it**, ready to run or to copy:
- `github_search_issues` takes a `query` (for example
`repo:acme/app is:open label:bug`) and returns the matching issues as
`search.content`.
- `github_get_issue` takes `owner`, `repo` and `issue_number` and returns the
issue as `issue.content`.

The app adds no objects, no sample data and no demo users. An install adds the
connector and the two flows, and nothing else.

## Credentials

The app needs one credential. Until it is set, the `github_mcp` connector is
listed as disconnected and its flows fail, naming the credential.

| Credential | What it is | Where to get it | Permissions it needs |
|:---|:---|:---|:---|
| `GITHUB_MCP_TOKEN` | A GitHub personal access token, sent to GitHub's MCP server as `Authorization: Bearer <token>` | GitHub → **Settings → Developer settings → Personal access tokens** (<https://github.com/settings/personal-access-tokens/new> for a fine-grained token) | Fine-grained token (GitHub's recommendation): access to the repositories you will search, with read-only **Issues** and **Pull requests** repository permissions; a repository owned by an organization also needs the organization to approve the token. Classic token: the `repo` scope, which both tools require |

Set the value in **Setup → Integrations → Connector Credentials**, or in the
install dialog right after installing. An environment variable named
`GITHUB_MCP_TOKEN` on the server takes precedence over a stored value.

Prefer a fine-grained token. With a classic token, GitHub's server hides every
tool whose scope the token lacks. If that hides `search_issues` or
`issue_read`, the connector does not start without them: a value set in
**Connector Credentials** leaves `github_mcp` disconnected with a reason naming
the missing tools, and a value set as the server environment variable stops the
server from starting at all. Replace the token with one that carries the `repo`
scope: a new value in **Connector Credentials** takes effect at once, a new
environment variable at the next start.

The token is only ever sent to GitHub. It is never stored in the app's
metadata.

## Running it

```bash
curl -X POST "$OS_URL/api/v1/automation/github_search_issues/trigger" \
-H "Authorization: Bearer $SESSION_TOKEN" -H 'Content-Type: application/json' \
-d '{"params": {"query": "repo:acme/app is:open label:bug"}}'
```

To use the answer elsewhere, add a `connector_action` node with
`connectorId: 'github_mcp'` to your own flow and read `<nodeId>.content` in a
later node.

## Limits

- Read only. The server is asked for read tools only (`X-MCP-Readonly: true`)
and the connector allowlists two of them.
- The token is a static personal token. Signing in with GitHub (OAuth) is not
offered.
36 changes: 36 additions & 0 deletions packages/apps/connector-github/objectstack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { defineStack } from '@objectstack/spec';
import { ConnectorMcpPlugin } from '@objectstack/connector-mcp';
import { GitHubMcpConnector } from './src/connectors/github.connector.js';
import { allFlows } from './src/flows/github.flows.js';

/**
* GitHub connector app — open edition (every key it uses runs on the open
* tier), so it lives in this repository and is validated by its CI against
* the runtime at HEAD. One declarative connector, two flows that use it; no
* objects, no secret. The listing is `objectstack.manifest.json`, and the
* README is what the administrator reads before installing.
*/
export default defineStack({
manifest: {
id: 'app.objectstack.connector-github',
namespace: 'github',
version: '0.1.0',
type: 'app',
name: 'GitHub',
description: "Search and read GitHub issues from flows, through GitHub's remote MCP server.",
engines: { protocol: '^18' },
},

// `automation` materializes the `connectors:` entry at boot and runs the
// flows; the executor below registers the `mcp` provider with it.
requires: ['automation'],

// The generic `mcp` executor, zero-arg: it contributes the provider factory
// only. The transport is http, so the stdio policy does not apply.
plugins: [new ConnectorMcpPlugin()],

connectors: [GitHubMcpConnector],
flows: allFlows,
});
13 changes: 13 additions & 0 deletions packages/apps/connector-github/objectstack.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://schemas.objectstack.dev/template-manifest.json",
"manifestId": "app.objectstack.connector-github",
"name": "connector-github",
"specVersion": "^18.0.0",
"displayName": "GitHub",
"description": "Search and read GitHub issues and pull requests from flows, through GitHub's remote MCP server. Read only.",
"category": "devtools",
"tags": ["github", "mcp", "connector", "issues"],
"license": "Apache-2.0",
"homepageUrl": "https://github.com/objectstack-ai/objectstack/tree/main/packages/apps/connector-github",
"readmePath": "README.md"
}
31 changes: 31 additions & 0 deletions packages/apps/connector-github/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@objectstack/connector-app-github",
"version": "0.1.0",
"description": "GitHub connector app (open edition): GitHub's remote MCP server as a declarative `mcp` connector instance with a read-tool allowlist, authenticated by a personal access token the administrator supplies through the credential store.",
"license": "Apache-2.0",
"private": true,
"type": "module",
"main": "./objectstack.config.ts",
"types": "./objectstack.config.ts",
"exports": {
".": "./objectstack.config.ts",
"./objectstack.config": "./objectstack.config.ts"
},
"scripts": {
"build": "objectstack build",
"validate": "objectstack validate",
"typecheck": "tsc --noEmit",
"test": "objectstack validate && vitest run"
},
"dependencies": {
"@objectstack/connector-mcp": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@objectstack/cli": "workspace:*",
"@objectstack/verify": "workspace:*",
"@types/node": "^26.6.5",
"typescript": "^6.0.3",
"vitest": "^4.1.11"
}
}
42 changes: 42 additions & 0 deletions packages/apps/connector-github/src/connectors/github.connector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { defineConnector } from '@objectstack/spec/integration';

/**
* GitHub over its remote MCP server — recipe 2 of the integration recipes
* (`skills/objectstack-automation/references/integration-recipes.md`): a
* declarative `provider: 'mcp'` instance (ADR-0097) the `mcp` executor
* materializes at boot by calling the server's `tools/list`.
*
* The server URL and the token header are GitHub's own
* (github/github-mcp-server `docs/remote-server.md` and the README's remote
* configuration): `https://api.githubcopilot.com/mcp/`, authenticated by
* `Authorization: Bearer <personal access token>`. The `bearer` auth below is
* folded into exactly that header by the executor; the token itself is never
* in this file — `credentialRef` names it, and the administrator supplies the
* value (an environment variable of that name, or Setup → Integrations →
* Connector Credentials).
*
* `X-MCP-Readonly: true` asks the server to list only its read tools, and
* `include` narrows those to the two this app's flows call. An `include`
* entry the server does not list is refused at materialization, never
* silently dropped.
*/
export const GitHubMcpConnector = defineConnector({
name: 'github_mcp',
label: 'GitHub (remote MCP)',
type: 'api',
description:
"GitHub's remote MCP server, read tools only: issue search and single-issue reads, authenticated by a " +
'personal access token the administrator supplies.',
provider: 'mcp',
providerConfig: {
transport: {
kind: 'http',
url: 'https://api.githubcopilot.com/mcp/',
headers: { 'X-MCP-Readonly': 'true' },
},
include: ['search_issues', 'issue_read'],
},
auth: { type: 'bearer', credentialRef: 'GITHUB_MCP_TOKEN' },
});
84 changes: 84 additions & 0 deletions packages/apps/connector-github/src/flows/github.flows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { defineFlow } from '@objectstack/spec';

/**
* Search GitHub issues and pull requests. Run it with
* `POST /api/v1/automation/github_search_issues/trigger` and a body of
* `{ "params": { "query": "repo:acme/app is:open label:bug" } }`; the tool's
* answer is the run's `search.content` output.
*/
export const GitHubSearchIssuesFlow = defineFlow({
name: 'github_search_issues',
label: 'GitHub: search issues',
description: "Searches issues and pull requests with GitHub's search syntax through the github_mcp connector.",
type: 'autolaunched',
status: 'active',
variables: [
{ name: 'query', type: 'text', isInput: true },
{ name: 'search.content', type: 'json', isOutput: true },
],
nodes: [
{ id: 'start', type: 'start', label: 'Start' },
{
id: 'search',
type: 'connector_action',
label: 'search_issues',
connectorConfig: {
connectorId: 'github_mcp',
actionId: 'search_issues',
input: { query: { dialect: 'cel', source: 'query' } },
},
},
{ id: 'end', type: 'end', label: 'End' },
],
edges: [
{ id: 'e1', source: 'start', target: 'search' },
{ id: 'e2', source: 'search', target: 'end' },
],
});

/**
* Read one issue. Run it with
* `POST /api/v1/automation/github_get_issue/trigger` and a body of
* `{ "params": { "owner": "acme", "repo": "app", "issue_number": 42 } }`; the
* issue is the run's `issue.content` output.
*/
export const GitHubGetIssueFlow = defineFlow({
name: 'github_get_issue',
label: 'GitHub: get issue',
description: 'Reads one issue (title, state, body, labels) through the github_mcp connector.',
type: 'autolaunched',
status: 'active',
variables: [
{ name: 'owner', type: 'text', isInput: true },
{ name: 'repo', type: 'text', isInput: true },
{ name: 'issue_number', type: 'number', isInput: true },
{ name: 'issue.content', type: 'json', isOutput: true },
],
nodes: [
{ id: 'start', type: 'start', label: 'Start' },
{
id: 'issue',
type: 'connector_action',
label: 'issue_read',
connectorConfig: {
connectorId: 'github_mcp',
actionId: 'issue_read',
input: {
method: 'get',
owner: { dialect: 'cel', source: 'owner' },
repo: { dialect: 'cel', source: 'repo' },
issue_number: { dialect: 'cel', source: 'issue_number' },
},
},
},
{ id: 'end', type: 'end', label: 'End' },
],
edges: [
{ id: 'e1', source: 'start', target: 'issue' },
{ id: 'e2', source: 'issue', target: 'end' },
],
});

export const allFlows = [GitHubSearchIssuesFlow, GitHubGetIssueFlow];
Loading
Loading