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
10 changes: 10 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export default defineConfig({
{ text: 'ai-autopilot', link: '/packages/ai-autopilot' },
{ text: 'ai-mcp', link: '/packages/ai-mcp' },
{ text: 'mcp', link: '/packages/mcp' },
{ text: 'connectors', link: '/packages/connectors' },
],
},
{
Expand Down Expand Up @@ -91,6 +92,15 @@ export default defineConfig({
{ text: 'mcp', link: '/packages/mcp' },
],
},
{
text: 'Connectors',
items: [
{ text: 'connectors — the contract', link: '/packages/connectors' },
{ text: 'The connector registry', link: '/packages/connectors-registry' },
{ text: 'connector-github', link: '/packages/connector-github' },
{ text: 'connector-google-drive', link: '/packages/connector-google-drive' },
],
},
],
},

Expand Down
51 changes: 51 additions & 0 deletions docs/packages/connector-github.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# @gemstack/connector-github

A GitHub connector for GemStack AI orchestration. Read and act on issues, pull requests, and repository files over the GitHub REST API. Built with [`@gemstack/connectors`](/packages/connectors); the result is a standard [`@gemstack/mcp`](/packages/mcp) server.

## Installation

```bash
npm i @gemstack/connector-github @gemstack/connectors @gemstack/mcp
```

## Use

```ts
import { mountConnectors } from '@gemstack/connectors'
import { Mcp } from '@gemstack/mcp'
import github from '@gemstack/connector-github'

const Server = mountConnectors([github], {
// A token with `repo` scope. A PAT or an OAuth bearer both work.
credentials: () => ({ token: process.env.GITHUB_TOKEN }),
})

Mcp.web('/mcp/github', Server)
```

Tools are exposed namespaced by connector id, e.g. `github_list-issues`.

## Auth

The connector declares `auth: { type: 'pat', env: 'GITHUB_TOKEN' }`. It only consumes a bearer token from `ctx.auth.token` — it does no OAuth handshake itself. To protect a web endpoint with OAuth 2.1, wrap it with `@gemstack/mcp`'s `oauth2McpMiddleware` + `registerOAuth2Metadata` and feed the verified token through the mount `credentials` option.

## Tools

| Tool | Kind | Description |
|---|---|---|
| `get-repo` | read | Repository metadata |
| `list-issues` | read | Issues (excludes PRs) |
| `get-issue` | read | One issue by number |
| `list-pull-requests` | read | Pull requests |
| `get-pull-request` | read | One PR by number |
| `get-file` | read | File contents (base64-decoded) |
| `search-issues` | read | Search issues/PRs (GitHub search syntax) |
| `comment-on-issue` | write | Comment on an issue or PR |
| `create-issue` | write | Open a new issue |

Read tools are annotated `readOnly` so an agent can auto-approve them; write tools are not. Responses are slimmed to the fields an agent needs (no raw API envelopes) to keep token usage down.

## See also

- [`@gemstack/connectors`](/packages/connectors) — the contract this is built on, and how to write your own.
- [The connector registry](/packages/connectors-registry) — the other published connectors.
51 changes: 51 additions & 0 deletions docs/packages/connector-google-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# @gemstack/connector-google-drive

A Google Drive connector for GemStack AI orchestration. Browse, read, and share Drive files over the Drive REST API (v3). Built with [`@gemstack/connectors`](/packages/connectors); the result is a standard [`@gemstack/mcp`](/packages/mcp) server.

## Installation

```bash
npm i @gemstack/connector-google-drive @gemstack/connectors @gemstack/mcp
```

## Use

```ts
import { mountConnectors } from '@gemstack/connectors'
import { Mcp } from '@gemstack/mcp'
import drive from '@gemstack/connector-google-drive'

const Server = mountConnectors([drive], {
// A Google OAuth 2.0 access token with a Drive scope.
credentials: () => ({ token: process.env.GOOGLE_ACCESS_TOKEN }),
})

Mcp.web('/mcp/drive', Server)
```

Tools are exposed namespaced by connector id, e.g. `google-drive_list-files`.

## Auth

Drive has no static API key — it is OAuth 2.0 only. The connector declares `auth: { type: 'oauth', scopes: ['https://www.googleapis.com/auth/drive'] }` and consumes a bearer token from `ctx.auth.token`; it does no OAuth handshake itself. Use `drive.readonly` if you only need the read tools. To protect a web endpoint and obtain the token, wrap it with `@gemstack/mcp`'s `oauth2McpMiddleware` + `registerOAuth2Metadata` and feed the verified token through the mount `credentials` option.

## Tools

| Tool | Kind | Description |
|---|---|---|
| `get-about` | read | Authenticated user + storage usage |
| `list-files` | read | List files (folder / raw query scoped) |
| `search-files` | read | Search by name and full-text content |
| `get-file` | read | Metadata for one file or folder |
| `get-file-content` | read | File as text (Docs exported, others downloaded) |
| `list-permissions` | read | Who has access to a file |
| `create-folder` | write | Create a folder |
| `share-file` | write | Grant access (create a permission) |
| `trash-file` | write (destructive) | Move a file to the trash (reversible) |

`get-file-content` exports Google Docs/Sheets/Slides to text/CSV and downloads everything else via `alt=media`. Read tools are annotated `readOnly` so an agent can auto-approve them; `trash-file` is marked `destructive`. Responses are slimmed to the fields an agent needs (no raw API envelopes) to keep token usage down.

## See also

- [`@gemstack/connectors`](/packages/connectors) — the contract this is built on, and how to write your own.
- [The connector registry](/packages/connectors-registry) — the other published connectors.
32 changes: 32 additions & 0 deletions docs/packages/connectors-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# The connector registry

Connectors are the open, copyable layer of GemStack orchestration. Anyone can ship one: build it with [`@gemstack/connectors`](/packages/connectors), publish it to npm under the `connector-*` convention, and an orchestrator can mount it next to the first-party connectors with no special blessing.

## First-party connectors

| Connector | Package | Auth | What it does |
|---|---|---|---|
| [GitHub](/packages/connector-github) | `@gemstack/connector-github` | PAT / OAuth bearer | Read and act on issues, pull requests, and repository files. |
| [Google Drive](/packages/connector-google-drive) | `@gemstack/connector-google-drive` | Google OAuth 2.0 | Browse, read, and share Drive files (Docs/Sheets/Slides exported to text). |

Both are thin connectors over a REST client on the [`@gemstack/connectors`](/packages/connectors) contract — read them as canonical examples when writing your own.

## Naming convention

Publish a connector package as **`connector-<service>`** (first-party: `@gemstack/connector-github`; third-party: `@your-scope/connector-acme` or `gemstack-connector-acme` unscoped). The connector's runtime `id` — the value that namespaces its tools — should match the service (`github`, `google-drive`, `acme`).

The convention is what makes connectors discoverable: a search for `connector-` on npm, or the GitHub topic [`gemstack-connector`](https://github.com/topics/gemstack-connector), surfaces the ecosystem the same way [Vike's extensions listing](https://vike.dev/extensions) does for Vike.

## Publish your own

1. Build it with [`@gemstack/connectors`](/packages/connectors) — start from `examples/connectors-quickstart`.
2. Name the package `connector-<service>` and add the `gemstack-connector` keyword + GitHub topic.
3. Declare its `auth` honestly (`none` / `pat` / `oauth`) so an orchestrator knows what credential to resolve.
4. Publish to npm. That's it — there's no registry to register with; the naming convention *is* the registry.

To get a third-party connector linked from this page, open a PR or an issue on [gemstack-land/gemstack](https://github.com/gemstack-land/gemstack/issues).

## See also

- [`@gemstack/connectors`](/packages/connectors) — the contract and the "writing a connector" guide.
- [`mcp`](/packages/mcp) — the server a mounted connector becomes.
140 changes: 140 additions & 0 deletions docs/packages/connectors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# @gemstack/connectors

The **connector contract** for GemStack AI orchestration. Define a tool connector to an external service (GitHub, Google Drive, ...) once with `defineConnector`, then compose any number of connectors into a single [`@gemstack/mcp`](/packages/mcp) server with `mountConnectors`.

A connector only **declares** what it needs (its auth requirement) and **what it does** (its tools). It never reaches for env vars, OAuth, or a transport itself. The orchestrator that mounts it supplies credentials and chooses how to serve it. That split is what lets first-party and third-party connectors compose interchangeably.

> Already looking for a specific service? See the [connector registry](/packages/connectors-registry) for the published connectors (GitHub, Google Drive) and the `connector-*` naming convention.

## Installation

```bash
npm i @gemstack/connectors @gemstack/mcp zod
```

The server it produces plugs into the same surface as any other `@gemstack/mcp` server (`Mcp.web` / `Mcp.local`, `McpTestClient`, the neutral HTTP handler).

## Writing a connector

A connector is one object passed to `defineConnector`: an `id` (used to namespace its tools), an `auth` requirement, and a list of `tools`. Here is the reference connector shipped in `examples/connectors-quickstart` — a read-only `library` over an in-memory list. Copy it to start a real one: swap the in-memory data for calls to your API, and change `auth` from `none` to `pat` / `oauth`.

```ts
import { defineConnector } from '@gemstack/connectors'
import { z } from 'zod'

const BOOKS = [
{ id: 'b1', title: 'The Pragmatic Programmer', author: 'Hunt & Thomas' },
{ id: 'b2', title: 'Refactoring', author: 'Fowler' },
]

export default defineConnector({
id: 'library', // lowercase letters, digits, '-'; namespaces this connector's tools
name: 'Reference Library',
instructions: 'A read-only demo connector over a small in-memory book list.',
auth: { type: 'none' }, // 'none' | 'pat' | 'oauth'
tools: [
{
name: 'list-books',
description: 'List every book in the library.',
schema: z.object({}),
annotations: { readOnly: true },
handle: () => BOOKS,
},
{
name: 'get-book',
description: 'Fetch one book by id.',
schema: z.object({ id: z.string() }),
annotations: { readOnly: true },
handle: (input: { id: string }, ctx) => {
// ctx.auth carries the credential the orchestrator resolved for 'library'.
return BOOKS.find((b) => b.id === input.id) ?? { error: `no book ${input.id}` }
},
},
],
})
```

### A tool

Each entry in `tools` is one tool the connector exposes:

| Field | Required | Purpose |
|---|---|---|
| `name` | yes | Unique within the connector. Kept verbatim, namespaced at mount (`library` + `get-book` -> `library_get-book`). |
| `description` | recommended | One line shown to the agent. |
| `schema` | yes | A Zod object (v3 or v4). The runtime source of truth for input validation. |
| `annotations` | no | Behavioural hints: `readOnly`, `destructive`, `idempotent`, `openWorld`. Let agents reason before calling (e.g. auto-approve `readOnly`). |
| `handle` | yes | `(input, ctx) => result`. Receives validated `input` and the connector [context](#the-handler-context). |

A `handle` may return a `string` (wrapped as text), any JSON-serializable value (wrapped as pretty JSON), or a full `McpToolResult` (use `McpResponse` from `@gemstack/mcp` for errors / images).

### The handler context

The second `handle` argument is the `ConnectorContext`:

- `ctx.connectorId` — the id of the connector this tool belongs to.
- `ctx.auth` — the `Credential` the orchestrator resolved for this connector (`{}` when none was provided). `ctx.auth.token` is the common case (a PAT or OAuth bearer).

A connector never reads `process.env` or runs an OAuth handshake itself — it reads `ctx.auth.token` and lets the orchestrator decide where that came from. See the [GitHub](/packages/connector-github) and [Google Drive](/packages/connector-google-drive) connectors for the same pattern over a real REST client.

## Auth requirements

A connector declares one of:

| `auth.type` | Means | Credential the orchestrator provides |
|---|---|---|
| `none` | Public / local data | `{}` |
| `pat` | Personal access token / API key (`env` names a default var) | `{ token }` |
| `oauth` | OAuth 2.1 bearer (`scopes`, `authorizationServers`) | `{ token }` |

For `oauth`, protect the mounted endpoint with `@gemstack/mcp`'s `oauth2McpMiddleware` + `registerOAuth2Metadata` and feed the verified token through `credentials` (below).

## Mount connectors into a server

`mountConnectors` composes any number of connectors into one standard `@gemstack/mcp` server class.

```ts
import { mountConnectors } from '@gemstack/connectors'
import { Mcp } from '@gemstack/mcp'
import library from './library-connector.js'
import github from '@gemstack/connector-github'

const Server = mountConnectors([library, github], {
name: 'my-orchestrator',
// Resolve each connector's credential at call time. This is the seam that
// satisfies a connector's declared `auth`.
credentials: (id) => ({ token: process.env[`${id.toUpperCase()}_TOKEN`] }),
})

Mcp.web('/mcp/connectors', Server) // a standard @gemstack/mcp server
```

Tools are namespaced by connector id, so `library.get-book` is exposed as `library_get-book` and never collides with another connector's `get-book`. Pass `namespace: 'none'` to keep names verbatim (you then own collision-avoidance).

## Testing

A mounted connector is a normal `@gemstack/mcp` server, so drive it with `McpTestClient` — no transport, no network:

```ts
import { McpTestClient } from '@gemstack/mcp/testing'
import { mountConnectors } from '@gemstack/connectors'
import library from './library-connector.js'

const client = new McpTestClient(mountConnectors([library]))

await client.listTools() // [{ name: 'library_list-books', ... }, ...]
await client.callTool('library_get-book', { id: 'b1' })
```

To test a connector that calls a real API, stub global `fetch` and assert on the requests it makes — that is exactly how the [GitHub](https://github.com/gemstack-land/gemstack/blob/main/packages/connector-github/src/index.test.ts) and [Google Drive](https://github.com/gemstack-land/gemstack/blob/main/packages/connector-google-drive/src/index.test.ts) connectors are tested.

## API

- `defineConnector(def): Connector` — validate the definition + fill defaults.
- `mountConnectors(connectors, options?): ConnectorServerClass` — compose into one `@gemstack/mcp` server class. Options: `name`, `version`, `instructions`, `namespace` (`'id'` default | `'none'`), and `credentials(id) => Credential`.

## See also

- [The connector registry](/packages/connectors-registry) — published connectors + the `connector-*` convention for shipping your own.
- [`mcp`](/packages/mcp) — the server framework a mounted connector becomes.
- [`ai-mcp`](/packages/ai-mcp) — feed a mounted connector's tools into an agent.
6 changes: 6 additions & 0 deletions docs/packages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All packages publish under the **`@gemstack/`** scope (e.g. `npm install @gemsta
| [`ai-autopilot`](/packages/ai-autopilot) | Orchestration: a Supervisor that plans, dispatches subagents (bounded concurrency + budget guardrails), and synthesizes the result. |
| [`ai-mcp`](/packages/ai-mcp) | The agent/MCP bridge: consume a remote MCP server's tools as agent tools, and expose an agent as an MCP server. |
| [`mcp`](/packages/mcp) | A standalone framework for *authoring* MCP servers: tools, resources, prompts, decorators, OAuth 2.1, a framework-neutral HTTP handler, and a test client. Agent-agnostic. |
| [`connectors`](/packages/connectors) | The connector contract: `defineConnector` declares a tool connector to an external service; `mountConnectors` composes any number into one `@gemstack/mcp` server. First-party connectors: [GitHub](/packages/connector-github), [Google Drive](/packages/connector-google-drive). |

## How they fit together

Expand All @@ -19,8 +20,13 @@ ai-autopilot orchestration / autonomy (the "director") -> ai-sdk (+ skills)
ai-mcp agent <-> MCP bridge (the "adapter") -> ai-sdk
-----------------------------------------------------------------------------------
mcp standalone MCP server framework agent-agnostic, not ai-*
connectors tool connectors to external services -> mcp (composes into one)
```

### Connectors

`connectors` builds *on* `mcp`: a connector declares an external service's auth requirement and tools, and `mountConnectors` composes any number of them into a single `mcp` server. It is the open, copyable layer — third parties ship their own `connector-*` packages that mount alongside the first-party [GitHub](/packages/connector-github) and [Google Drive](/packages/connector-google-drive) connectors. See [the connector registry](/packages/connectors-registry).

### Two MCP packages, two jobs

`ai-mcp` and `mcp` both touch the Model Context Protocol, but from opposite ends:
Expand Down
Loading