From e08641bc90c3bd9b12365e3eee4ee07a3ff5da5e Mon Sep 17 00:00:00 2001 From: penghanyuan Date: Sat, 7 Mar 2026 22:27:28 +0100 Subject: [PATCH 1/2] Update dependencies in pnpm-lock.yaml, enhance README with detailed agent questions and solutions, and improve documentation structure. Added new deployment guide and quick start instructions, while refining existing content for clarity and consistency. --- README.md | 13 +- docs/content/docs/concepts/agents.mdx | 20 +- docs/content/docs/concepts/architecture.mdx | 23 +- docs/content/docs/concepts/confirmations.mdx | 11 +- docs/content/docs/concepts/credentials.mdx | 13 +- .../docs/concepts/mcp-access-control.mdx | 16 +- docs/content/docs/concepts/meta.json | 2 +- docs/content/docs/concepts/policies.mdx | 21 +- .../docs/getting-started/configuration.mdx | 65 -- .../docs/getting-started/deployment.mdx | 183 +++++ docs/content/docs/getting-started/index.mdx | 63 +- .../docs/getting-started/installation.mdx | 59 -- docs/content/docs/getting-started/meta.json | 2 +- .../docs/getting-started/quick-start.mdx | 112 +++ docs/content/docs/guides/agent-creator.mdx | 24 +- docs/content/docs/guides/app-builder.mdx | 561 ++++++------- docs/content/docs/guides/gateway-admin.mdx | 777 +++++++----------- docs/content/docs/index.mdx | 44 - docs/content/docs/meta.json | 14 +- docs/content/docs/sdks/python.mdx | 6 +- docs/package.json | 3 + docs/source.config.ts | 3 +- docs/src/app/(home)/page.tsx | 52 +- docs/src/app/docs/docs-layout-client.tsx | 32 + docs/src/app/docs/layout.tsx | 7 +- docs/src/app/global.css | 9 + docs/src/components/mermaid.tsx | 77 ++ docs/src/lib/layout.shared.tsx | 17 +- docs/src/mdx-components.tsx | 2 + pnpm-lock.yaml | 117 +++ 30 files changed, 1243 insertions(+), 1105 deletions(-) delete mode 100644 docs/content/docs/getting-started/configuration.mdx create mode 100644 docs/content/docs/getting-started/deployment.mdx delete mode 100644 docs/content/docs/getting-started/installation.mdx create mode 100644 docs/content/docs/getting-started/quick-start.mdx delete mode 100644 docs/content/docs/index.mdx create mode 100644 docs/src/app/docs/docs-layout-client.tsx create mode 100644 docs/src/components/mermaid.tsx diff --git a/README.md b/README.md index 2d0330a..92c5963 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,14 @@ An open-source Agent Gateway that gives your AI agents a secure foundation — i AI agents are increasingly autonomous — they call APIs, access sensitive data, and take real-world actions on behalf of users. But most agent frameworks lack the infrastructure to do this safely: -- **Who is this agent?** No standard identity or authentication model. -- **What can it access?** No fine-grained access control for tools and APIs. -- **Where are the credentials?** Secrets are hardcoded or scattered across configs. -- **Did anyone approve this?** No human-in-the-loop for high-risk operations. -- **What happened?** No audit trail when things go wrong. +Simplaix Gateway is the infrastructure layer that answers all of these questions. + +| Question | The gap | +|----------|---------| +| **Who is this agent?** | Agents have no standard identity or authentication model. Any request claiming to be an agent is trusted implicitly. | +| **What is it allowed to do?** | There is no fine-grained access control over which tools and APIs an agent can invoke on behalf of which user. | +| **Did anyone approve this?** | High-risk operations — deleting data, sending messages, moving money — execute silently with no human checkpoint. | +| **What actually happened?** | When something goes wrong, there is no structured record of ***who asked which agent to do what, and when***. | Simplaix Gateway sits between your agents and the outside world, solving all of these problems in one layer. diff --git a/docs/content/docs/concepts/agents.mdx b/docs/content/docs/concepts/agents.mdx index 65c5ae4..71ffe5d 100644 --- a/docs/content/docs/concepts/agents.mdx +++ b/docs/content/docs/concepts/agents.mdx @@ -7,12 +7,13 @@ Each agent in the Gateway has a virtual identity that includes its upstream URL, ## Agent Registration -Agents are registered by admin users and linked to a tenant. Each agent has: +Agents are registered by authorized admin/creator roles and linked to a tenant. Each agent has: - **Name** -- human-readable identifier - **Upstream URL** -- where the agent runtime is hosted - **Required Credentials** -- credential types the agent needs (e.g., `gateway_api`, `slack`) - **Kill Switch** -- ability to disable an agent immediately +- **Runtime Token** -- one-time `art_...` token for agent-to-gateway calls ```bash POST /api/v1/admin/agents @@ -26,13 +27,14 @@ POST /api/v1/admin/agents ## Agent Invocation Flow -When the Gateway invokes an agent (via `/v1/agents/:id/invoke`), it performs a full pre-flight: +When the Gateway invokes an agent (via `/api/v1/agents/:id/invoke`), it performs a full pre-flight: -1. **Authenticate** the user via JWT +1. **Authenticate** via flexible auth (JWT, API key + JWT/User-ID, or runtime token where applicable) 2. **Load agent** and check tenant isolation + kill switch 3. **Resolve credentials** -- if `requiredCredentials` are configured and any are missing, return 401 with auth URLs 4. **Inject headers** -- user identity + resolved credentials as `X-Credential-*` -5. **Forward** to the agent's `upstreamUrl` and stream the response back +5. **Issue session token (when enabled)** -- attach `X-Gateway-Session-Token` so downstream MCP calls can preserve end-user identity +6. **Forward** to the agent's `upstreamUrl` and stream the response back ## Injected Headers @@ -47,9 +49,17 @@ When forwarding requests to agent runtimes, the Gateway injects these headers: | `X-Tenant-ID` | Tenant ID | | `X-Gateway-Agent-ID` | Agent UUID | | `X-Gateway-Request-ID` | Unique request ID | +| `X-Gateway-Session-Token` | Short-lived session JWT for MCP callbacks as the real end-user | | `X-Credential-{service}` | Resolved credential value per service type | +| `Authorization` | Optional upstream bearer token when `upstreamSecret` is configured | -The agent runtime can use `X-Gateway-Agent-ID` to identify itself when calling back into the Gateway's tool proxy (as `X-Agent-Id`). +For MCP callback routes, runtimes typically send `X-Agent-Id` plus `X-Gateway-Session-Token`, or authenticate directly with the agent runtime token (`art_...`). + +## Runtime Token Lifecycle + +- Created once on `POST /api/v1/admin/agents` +- Can be rotated with `POST /api/v1/admin/agents/:id/regenerate-token` +- Stored hashed in the database; plaintext is returned only when created/rotated ## Kill Switch diff --git a/docs/content/docs/concepts/architecture.mdx b/docs/content/docs/concepts/architecture.mdx index f06a8fa..4e3fac9 100644 --- a/docs/content/docs/concepts/architecture.mdx +++ b/docs/content/docs/concepts/architecture.mdx @@ -22,17 +22,21 @@ flowchart TB AuthMW[Auth Middleware] JWT[JWT Verifier] APIKeyAuth[API Key Auth] + ART[Runtime Token Auth] end subgraph core [Core Services] Policy[Policy Engine] + ACL[Provider Access Service] Pauser[Request Pauser] AgentSvc[Agent Service] CredSvc[Credential Service] CredProviders[Credential Providers] + Aggregation[Tool Aggregation] end subgraph proxy [Proxy Layer] + MCPUnified[Unified MCP] MCPProxy[MCP Proxy] AgentInvoke[Agent Invoke] HeaderInjector[Identity + Credential Injector] @@ -57,14 +61,20 @@ flowchart TB end FE -->|JWT| AuthMW + AI -->|art_ token| ART AI -->|gk_ + X-Agent-Id| APIKeyAuth SDK -->|gk_ API key| APIKeyAuth AuthMW --> JWT APIKeyAuth --> CredSvc + ART --> AgentSvc JWT --> Policy APIKeyAuth --> Policy + ART --> Policy + Policy --> ACL + ACL --> Aggregation + Aggregation --> MCPUnified Policy --> Pauser Pauser --> MCPProxy @@ -72,6 +82,7 @@ flowchart TB CredSvc --> Encryption Encryption --> DB + MCPUnified --> HeaderInjector MCPProxy --> HeaderInjector AgentInvoke --> HeaderInjector HeaderInjector --> MCP1 @@ -90,26 +101,30 @@ flowchart TB ### Authentication Layer -The Gateway supports two authentication methods: +The Gateway supports three authentication methods: - **JWT** -- for admin operations and agent invocation from frontends - **API Keys (`gk_`)** -- for server-to-server communication between agent runtimes and the Gateway +- **Agent Runtime Tokens (`art_`)** -- runtime identity for registered agents See [Authentication](/docs/authentication) for details. ### Core Services -- **Policy Engine** -- evaluates rules to allow, deny, or require confirmation for tool calls +- **Provider Access Service** -- enforces provider-level ACL and tool-level policy rules from the database +- **Policy Engine** -- fallback config policy layer (used when DB rules are unavailable) - **Request Pauser** -- holds requests pending human confirmation decisions - **Agent Service** -- manages agent registration, configuration, and lifecycle - **Credential Service** -- encrypted credential storage and runtime resolution - **Credential Providers** -- defines how each credential type (OAuth2, API key, JWT, basic) works +- **Tool Aggregation** -- merges and filters tools across accessible providers for unified MCP ### Proxy Layer -- **MCP Proxy** -- routes MCP JSON-RPC tool calls to upstream servers with policy enforcement +- **Unified MCP Endpoint** -- single `/api/v1/mcp/mcp` endpoint that aggregates tools from all authorized providers +- **MCP Proxy** -- per-provider endpoint (`/api/v1/mcp-proxy/:providerId/mcp`) for direct proxying - **Agent Invoke** -- protocol-agnostic agent invocation with credential pre-checking (supports JSON and SSE streaming responses) -- **Header Injector** -- injects user identity and resolved credentials into upstream requests +- **Header Injector** -- injects end-user/agent identity and provider auth headers into upstream requests ### Data Layer diff --git a/docs/content/docs/concepts/confirmations.mdx b/docs/content/docs/concepts/confirmations.mdx index e355a83..db73a86 100644 --- a/docs/content/docs/concepts/confirmations.mdx +++ b/docs/content/docs/concepts/confirmations.mdx @@ -17,14 +17,14 @@ sequenceDiagram participant SSE as SSE Stream participant U as Confirmer - C->>GW: POST /v1/mcp/tools/call + C->>GW: POST /api/v1/mcp/mcp or /api/v1/mcp-proxy/:providerId/mcp GW->>PE: Evaluate policy for tool PE-->>GW: require_confirmation GW->>RP: Pause request RP->>SSE: Emit CONFIRMATION_REQUIRED SSE->>U: Show decision card - U->>GW: POST /v1/confirmation/:id/confirm + U->>GW: POST /api/v1/confirmation/:id/confirm GW->>RP: Resume request RP-->>GW: Forward to upstream GW-->>C: Response @@ -32,11 +32,11 @@ sequenceDiagram ## How It Works -1. A tool call arrives that matches a `require_confirmation` policy +1. A tool call arrives on MCP proxy/unified MCP (or tool-gate) and matches `require_confirmation` 2. The Gateway creates a confirmation record with status `pending` 3. The request is paused using the Request Pauser service 4. A `CONFIRMATION_REQUIRED` event is emitted on the SSE stream -5. A confirmer (admin or designated user) sees the confirmation card in the dashboard +5. A confirmer (the end-user or an admin in the same tenant) sees the confirmation card in the dashboard 6. The confirmer confirms or rejects the request 7. If confirmed, the paused request is resumed and forwarded to the upstream server 8. If rejected, the request is denied with an appropriate response @@ -47,8 +47,7 @@ sequenceDiagram |--------|-------------| | `pending` | Awaiting human decision | | `confirmed` | Confirmed by a human, request will proceed | -| `rejected` | Rejected by a human, request is denied | -| `expired` | Timed out without a decision | +| `rejected` | Rejected by a human, or timed out with reason `Request timed out` | ## SSE Stream diff --git a/docs/content/docs/concepts/credentials.mdx b/docs/content/docs/concepts/credentials.mdx index dbb079b..0369452 100644 --- a/docs/content/docs/concepts/credentials.mdx +++ b/docs/content/docs/concepts/credentials.mdx @@ -17,12 +17,12 @@ sequenceDiagram Note over User,Agent: 1. User connects a service User->>FE: Click "Connect Gateway API" - FE->>GW: POST /v1/credentials/jwt + FE->>GW: POST /api/v1/credentials/jwt GW->>Vault: Store encrypted credential Note over User,Agent: 2. User chats with agent User->>FE: "Show me all agents" - FE->>GW: POST /v1/agents/:id/invoke + JWT + FE->>GW: POST /api/v1/agents/:id/invoke + JWT Note over GW: 3. Gateway pre-checks credentials GW->>Vault: Resolve requiredCredentials @@ -56,7 +56,7 @@ POST /api/v1/credential-providers | Auth Type | Description | |-----------|-------------| -| `oauth2` | OAuth 2.0 flow with refresh tokens | +| `oauth2` | OAuth provider type is supported; full callback/token exchange flow is currently placeholder | | `api_key` | Static API key | | `jwt` | JSON Web Token | | `basic` | Basic authentication (username/password) | @@ -65,8 +65,11 @@ POST /api/v1/credential-providers Agents declare `requiredCredentials` in their configuration. The Gateway resolves these before forwarding requests: -- **Agent invoke route** (`/v1/agents/:id/invoke`): Pre-checks credentials. Returns `CREDENTIALS_REQUIRED` if missing, or injects `X-Credential-*` headers if available. -- **MCP proxy** (`/v1/mcp/tools/call`): Same pattern -- resolves and injects credentials into upstream headers. +- **Agent invoke route** (`/api/v1/agents/:id/invoke`): Pre-checks credentials. Returns `CREDENTIALS_REQUIRED` if missing, or injects `X-Credential-*` headers if available. +- **Credential check route** (`/api/v1/agents/:id/credentials-check`): Lightweight preflight check used before invoke/stream starts. +- **Resolve API** (`/api/v1/credentials/resolve`): Internal API for SDK/runtime flows that need explicit credential lookup. + +MCP proxy routes do **not** auto-inject per-user vault credentials as `X-Credential-*`; they forward identity and provider-auth headers. ## Encryption diff --git a/docs/content/docs/concepts/mcp-access-control.mdx b/docs/content/docs/concepts/mcp-access-control.mdx index d7726ba..36cec83 100644 --- a/docs/content/docs/concepts/mcp-access-control.mdx +++ b/docs/content/docs/concepts/mcp-access-control.mdx @@ -17,7 +17,7 @@ Access rules are scoped to a **subject** -- the entity making the request: | `user` | An individual end-user (by user ID) | **Deny** (whitelist model) | | `agent` | A registered agent (by agent ID) | **Deny** (whitelist model) | -All subjects are denied access to all providers by default. An admin must explicitly grant access to each provider by creating `allow` rules. This whitelist model ensures no provider is accidentally exposed without deliberate configuration. +All subjects are denied access to all providers by default. An admin must explicitly grant access to each provider by creating `allow` (or `require_confirmation`) rules. This whitelist model ensures no provider is accidentally exposed without deliberate configuration. ## Provider-Level ACL @@ -38,9 +38,9 @@ Each rule grants or denies a subject access to a provider: When multiple rules match, the Gateway evaluates in this order (first match wins): 1. Explicit **user deny** → denied -2. Explicit **user allow** → allowed +2. Explicit **user allow/require_confirmation** → allowed 3. Explicit **agent deny** → denied -4. Explicit **agent allow** → allowed +4. Explicit **agent allow/require_confirmation** → allowed 5. **Wildcard** (`*`) provider rules 6. **Default** → deny for all subjects (whitelist model) @@ -107,7 +107,7 @@ GET /api/v1/admin/provider-access?subject_type=agent&subject_id={agentId} **Bulk upsert rules for an agent:** ```bash -curl -X PUT http://localhost:3001/api/v1/admin/provider-access/agent/ \ +curl -X PUT https:///api/v1/admin/provider-access/agent/ \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -122,7 +122,7 @@ curl -X PUT http://localhost:3001/api/v1/admin/provider-access/agent/ **Create a single rule:** ```bash -curl -X POST http://localhost:3001/api/v1/admin/provider-access \ +curl -X POST https:///api/v1/admin/provider-access \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -138,7 +138,7 @@ curl -X POST http://localhost:3001/api/v1/admin/provider-access \ **Test policy evaluation (dry run):** ```bash -curl -X POST http://localhost:3001/api/v1/admin/provider-access/evaluate \ +curl -X POST https:///api/v1/admin/provider-access/evaluate \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -147,9 +147,11 @@ curl -X POST http://localhost:3001/api/v1/admin/provider-access/evaluate \ "toolName": "slack_send_message", "agentId": "agent-id" }' -# Response: { "action": "require_confirmation", "rule": { ... } } +# Response: { "action": "require_confirmation", "risk": "high", "matchedRule": { ... } } ``` +If there are zero DB rules available for `evaluate`, the gateway falls back to `src/config.ts` policy rules. If DB rules exist but none match the tool pattern, the result is deny. In normal MCP proxy/unified flows, provider-level ACL still gates access first (default deny). + ## Examples ### Read-only agent diff --git a/docs/content/docs/concepts/meta.json b/docs/content/docs/concepts/meta.json index da0b6f5..997229f 100644 --- a/docs/content/docs/concepts/meta.json +++ b/docs/content/docs/concepts/meta.json @@ -1,5 +1,5 @@ { "title": "Concepts", - "defaultOpen": true, + "defaultOpen": false, "pages": ["architecture", "agents", "credentials", "policies", "mcp-access-control", "confirmations"] } diff --git a/docs/content/docs/concepts/policies.mdx b/docs/content/docs/concepts/policies.mdx index a8cb225..3c6988e 100644 --- a/docs/content/docs/concepts/policies.mdx +++ b/docs/content/docs/concepts/policies.mdx @@ -3,17 +3,24 @@ title: Policy Engine description: Configurable rules for controlling tool access with allow, deny, and confirmation actions. --- -The policy engine evaluates rules to determine whether a tool call should be allowed, denied, or require human confirmation. +The policy engine determines whether a tool call should be allowed, denied, or require human confirmation. + +In current gateway flow: + +- **Primary policy source**: provider access rules in `provider_access_rules` (see [MCP Access Control](/docs/concepts/mcp-access-control)) +- **Fallback policy source**: static config rules in `src/config.ts` (used when DB rules are unavailable for a direct policy evaluation path) ## Configuration -Policies are configured in `src/config.ts`: +Default fallback policies are configured in `src/config.ts`: ```typescript const policies = [ { tool: 'transfer_money', action: 'require_confirmation', risk: 'high' }, { tool: 'delete_*', action: 'require_confirmation', risk: 'critical' }, + { tool: 'write_*', action: 'require_confirmation', risk: 'medium' }, { tool: 'read_*', action: 'allow', risk: 'low' }, + { tool: '*', action: 'allow', risk: 'low' }, ]; ``` @@ -41,12 +48,14 @@ Tool names support wildcard matching with `*`. When an MCP tool call arrives at the Gateway: 1. The **auth middleware** authenticates the request (JWT or API key) -2. The **policy engine** matches the tool name against configured rules -3. Based on the matched policy action: +2. The **provider access service** checks provider ACL (default deny / whitelist model) +3. The **tool policy evaluator** selects the best matching DB rule for `toolPattern` (exact > glob > wildcard, user > agent, deny > require_confirmation > allow) +4. If there are zero DB rules for that subject/provider evaluation, the **config fallback policy** from `src/config.ts` is used +5. Based on the final action: - `allow`: The request is forwarded to the upstream server - `deny`: A 403 response is returned immediately - `require_confirmation`: The request is paused and a confirmation notification is sent via SSE -4. After confirmation (or rejection), the request is either forwarded or denied -5. The **audit service** logs the result +6. After confirmation (or rejection), the request is either forwarded or denied +7. The **audit service** logs the result See [Confirmations](/docs/concepts/confirmations) for details on the human-in-the-loop confirmation flow. diff --git a/docs/content/docs/getting-started/configuration.mdx b/docs/content/docs/getting-started/configuration.mdx deleted file mode 100644 index 120ef8e..0000000 --- a/docs/content/docs/getting-started/configuration.mdx +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Configuration -description: Environment variables and configuration options for Simplaix Gateway. ---- - -## Environment Variables - -Create a `.env` file from the template: - -```bash -cp .env.example .env -``` - -### Core Settings - -| Variable | Description | Default | -|----------|-------------|---------| -| `PORT` | Server port | `3001` | -| `JWT_SECRET` | Secret key for JWT signing | (required) | -| `DATABASE_TYPE` | Database backend (`sqlite` or `postgres`) | `sqlite` | -| `SQLITE_PATH` | Path to SQLite database file | `./data/gateway.db` | -| `CREDENTIAL_ENCRYPTION_KEY` | 64-character hex string for AES-256-GCM encryption | (required) | -| `MCP_SERVER_URL` | Default upstream MCP server URL (fallback) | `http://localhost:8080` | - -### Example Configuration - -```ini -# Server -PORT=3001 - -# JWT -JWT_SECRET=your-secret-key - -# Database (sqlite or postgres) -DATABASE_TYPE=sqlite -SQLITE_PATH=./data/gateway.db - -# Credential encryption (64-char hex string) -CREDENTIAL_ENCRYPTION_KEY=your-64-char-hex-key - -# Default MCP server (fallback) -MCP_SERVER_URL=http://localhost:8080 -``` - -### Generating an Encryption Key - -You can generate a secure 64-character hex encryption key with: - -```bash -openssl rand -hex 32 -``` - -## Policy Configuration - -Policies are configured in `src/config.ts`: - -```typescript -const policies = [ - { tool: 'transfer_money', action: 'require_confirmation', risk: 'high' }, - { tool: 'delete_*', action: 'require_confirmation', risk: 'critical' }, - { tool: 'read_*', action: 'allow', risk: 'low' }, -]; -``` - -See [Policies](/docs/concepts/policies) for more details on configuring the policy engine. diff --git a/docs/content/docs/getting-started/deployment.mdx b/docs/content/docs/getting-started/deployment.mdx new file mode 100644 index 0000000..22d857d --- /dev/null +++ b/docs/content/docs/getting-started/deployment.mdx @@ -0,0 +1,183 @@ +--- +title: Deployment +description: Install and run Simplaix Gateway in production. +--- + +## Prerequisites + +- **Node.js** 22+ (or Docker) +- **PostgreSQL** 15+ (recommended for production; SQLite is supported for single-node deployments) +- **pnpm** (if building from source) + +--- + +## Option 1: Docker Compose (Recommended) + +The fastest way to get a production-ready Gateway with PostgreSQL. + +**1. Clone the repository** + +```bash +git clone https://github.com/simplaix/simplaix-gateway.git +cd simplaix-gateway +``` + +**2. Create your `.env`** + +```bash +cp .env.example .env +``` + +Edit `.env` with production values: + +```bash +JWT_SECRET= # openssl rand -hex 32 +CREDENTIAL_ENCRYPTION_KEY= # openssl rand -hex 32 +ADMIN_EMAIL=admin@yourdomain.com +ADMIN_PASSWORD= +POSTGRES_PASSWORD= +GATEWAY_PUBLIC_URL=https://gateway.yourdomain.com +``` + +**3. Start** + +```bash +docker compose up -d +``` + +The Gateway starts on port `3001` and PostgreSQL on `5432`. The admin user is created automatically on first boot from `ADMIN_EMAIL` / `ADMIN_PASSWORD`. + +--- + +## Option 2: Build from Source + +**1. Install dependencies** + +```bash +git clone https://github.com/simplaix/simplaix-gateway.git +cd simplaix-gateway +pnpm install +``` + +**2. Build** + +```bash +pnpm build +``` + +**3. Configure** + +```bash +cp .env.example .env +# Edit .env with your production values +``` + +**4. Run migrations** + +```bash +pnpm db:migrate +``` + +**5. Start** + +```bash +node dist/server.js +``` + +--- + +## Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `DATABASE_URL` | Yes | PostgreSQL connection string — `postgresql://user:pass@host/db` | +| `JWT_SECRET` | Yes | Secret for signing/verifying JWTs (min 32 chars) | +| `CREDENTIAL_ENCRYPTION_KEY` | Yes | 32-byte hex key for encrypting stored credentials | +| `PORT` | No | HTTP port (default: `3001`) | +| `JWT_ISSUER` | No | JWT issuer claim (default: `simplaix-gateway`) | +| `JWT_AUDIENCE` | No | JWT audience claim | +| `JWT_EXTERNAL_ISSUERS` | No | JSON array of external OIDC/JWT issuers | +| `ADMIN_EMAIL` | No | Auto-create admin on first boot | +| `ADMIN_PASSWORD` | No | Password for auto-created admin | +| `GATEWAY_PUBLIC_URL` | No | Public base URL — used in OAuth callbacks and pairing links | +| `OAUTH_CALLBACK_BASE_URL` | No | Override OAuth callback base separately from `GATEWAY_PUBLIC_URL` | + +### Generating secrets + +```bash +# JWT secret +openssl rand -hex 32 + +# Credential encryption key +openssl rand -hex 32 +``` + +### External JWT issuers + +To accept JWTs from an external identity provider (e.g. Azure AD, Auth0): + +```bash +# Shared secret +JWT_EXTERNAL_ISSUERS='[{"issuer":"https://auth.example.com","secret":"shared-secret","audience":"simplaix-gateway"}]' + +# OIDC / JWKS (Azure AD, etc.) +JWT_EXTERNAL_ISSUERS='[{"issuer":"https://login.microsoftonline.com/TENANT/v2.0","jwksUri":"https://login.microsoftonline.com/TENANT/discovery/v2.0/keys","audience":"api://simplaix-gateway"}]' +``` + +--- + +## Database + +### PostgreSQL (production) + +```bash +DATABASE_URL=postgresql://gateway:password@localhost:5432/gateway +``` + +### SQLite (single-node) + +```bash +DATABASE_URL=file:./gateway.db +``` + +SQLite requires no setup but does not support horizontal scaling. Use PostgreSQL for any multi-instance deployment. + +--- + +## Create the First Admin + +If you did not set `ADMIN_EMAIL` / `ADMIN_PASSWORD`, create an admin manually after the server is running: + +```bash +gateway admin create --email admin@example.com --password changeme --name "Admin" +``` + +Or via the API: + +```bash +curl -X POST https:///api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"email":"admin@example.com","password":"changeme"}' +``` + +--- + +## Verify + +```bash +gateway status +``` + +Or check the health endpoint: + +```bash +curl https:///health +``` + +## Next Steps + + + + + + diff --git a/docs/content/docs/getting-started/index.mdx b/docs/content/docs/getting-started/index.mdx index fe68877..c59deaf 100644 --- a/docs/content/docs/getting-started/index.mdx +++ b/docs/content/docs/getting-started/index.mdx @@ -1,39 +1,56 @@ --- -title: Overview -description: Get up and running with Simplaix Gateway in minutes. +title: What is Simplaix Gateway? +description: Enterprise-grade Agent Gateway providing identity, security, credential management, and policy enforcement for AI agents. --- -This guide will help you get Simplaix Gateway running locally. The Gateway is a Hono-based server that provides identity, security, credential management, and policy enforcement for AI agents. It supports multiple agent protocols including MCP, CopilotKit, AG-UI/Strands, and any HTTP-based agent runtime. +Simplaix Gateway is an enterprise-grade Agent Gateway that provides identity, security, credential management, and policy enforcement for AI agents. It supports multiple agent protocols including [MCP](https://modelcontextprotocol.io/), CopilotKit, AG-UI/Strands, and any HTTP-based agent runtime. -## Prerequisites +## The problem -- **Node.js** 18+ -- **pnpm** package manager -- **Python 3.12+** (optional, for the agent runtime) +Deploying AI agents in production surfaces a set of questions that no agent framework answers out of the box: -## Quick Start +Simplaix Gateway is the infrastructure layer that answers all of these questions. -```bash -# Clone the repository -git clone https://github.com/simplaix/simplaix-gateway.git -cd simplaix-gateway +| Question | The gap | +|----------|---------| +| **Who is this agent?** | Agents have no standard identity or authentication model. Any request claiming to be an agent is trusted implicitly. | +| **What is it allowed to do?** | There is no fine-grained access control over which tools and APIs an agent can invoke on behalf of which user. | +| **Did anyone approve this?** | High-risk operations — deleting data, sending messages, moving money — execute silently with no human checkpoint. | +| **What actually happened?** | When something goes wrong, there is no structured record of ***who asked which agent to do what, and when***. | -# Install dependencies -pnpm install -# Copy environment template -cp .env.example .env +## Key Features -# Start the Gateway server -pnpm dev -``` +- **Multi-Protocol Agent Routing** -- Route requests to any HTTP-based agent runtime (MCP servers, CopilotKit agents, Strands/AG-UI agents, custom runtimes) +- **Virtual Agent Identity** -- Register agents with upstream URLs, kill switch, and tenant isolation +- **Dual Authentication** -- JWT for admins and end-users, API Keys (`gk_`) for server-to-server +- **Credential Vault** -- Encrypted per-user credential storage with automatic resolution and injection +- **Policy Engine** -- Configurable rules: allow, deny, or require human confirmation per tool +- **Human-in-the-Loop Confirmation** -- SSE-based real-time confirmation workflow for sensitive operations +- **Comprehensive Audit Trail** -- Track every tool call with full context and timing +- **Multi-Tenancy** -- Tenant isolation across agents, credentials, and users +- **Credential SDKs** -- Python and TypeScript SDKs for agents to resolve user credentials -The Gateway will start on `http://localhost:3001`. +## Supported Agent Protocols -## Next Steps +| Protocol | Integration Point | Description | +|----------|------------------|-------------| +| **MCP** | `/api/v1/mcp/*` | JSON-RPC tool calls to MCP servers with policy enforcement | +| **Any agent runtime** | `/api/v1/agents/:id/invoke` | Any HTTP-based agent runtime (framework-agnostic) | + +## Guides by Role + + + + + + + +## Quick Links - - + + + diff --git a/docs/content/docs/getting-started/installation.mdx b/docs/content/docs/getting-started/installation.mdx deleted file mode 100644 index d3fa7d6..0000000 --- a/docs/content/docs/getting-started/installation.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Installation -description: Install and set up Simplaix Gateway and its components. ---- - -## Gateway Server - -The Gateway server is a Hono-based Node.js application. Install and run it with: - -```bash -# Install dependencies -pnpm install - -# Copy environment template -cp .env.example .env - -# Start the Gateway server (development) -pnpm dev -``` - -The server starts on port `3001` by default. - -## Dashboard (Optional) - -The dashboard is a Next.js application with an AI assistant powered by CopilotKit: - -```bash -# In a separate terminal -cd gateway-app -pnpm dev -``` - -## Python Agent (Optional) - -The included Python agent demonstrates how an agent runtime interacts with the Gateway: - -```bash -# In a separate terminal -cd gateway-app/agent -uv run main.py -``` - -## Project Structure - -``` -simplaix-gateway/ - src/ # Gateway server (Hono) - routes/ # API route handlers - services/ # Business logic - middleware/ # Hono middleware - db/ # Database (Drizzle ORM) - types/ # TypeScript types - gateway-app/ # Next.js dashboard + AI assistant - src/app/ # App Router pages - src/components/ # React components - agent/ # Python agent (Strands + AG-UI) - packages/ - credential-sdk-python/ # Python credential SDK -``` diff --git a/docs/content/docs/getting-started/meta.json b/docs/content/docs/getting-started/meta.json index c00cd32..6e92f33 100644 --- a/docs/content/docs/getting-started/meta.json +++ b/docs/content/docs/getting-started/meta.json @@ -1,5 +1,5 @@ { "title": "Getting Started", "defaultOpen": true, - "pages": ["index", "installation", "configuration"] + "pages": ["index", "overview", "installation", "configuration"] } diff --git a/docs/content/docs/getting-started/quick-start.mdx b/docs/content/docs/getting-started/quick-start.mdx new file mode 100644 index 0000000..1daf75c --- /dev/null +++ b/docs/content/docs/getting-started/quick-start.mdx @@ -0,0 +1,112 @@ +--- +title: Quick Start +description: Get Simplaix Gateway running locally with npm in minutes. +--- + +Get the Gateway running locally in under a minute using the `@simplaix/simplaix-gateway` npm package. + +> The server installed with `npm` should not be used in production. See full installation instructions [here](/docs/getting-started/deployment). + +## Prerequisites + +- **Node.js** 18+ + +## Install + +```bash +npm install -g @simplaix/simplaix-gateway +``` + +## Start the server + +```bash +gateway init +gateway start +``` + +`gateway init` creates a `.env` file with auto-generated secrets. `gateway start` starts the Gateway on `http://localhost:7521`. + +Then create your first admin user: + +```bash +gateway admin create --email admin@example.com --password changeme +``` + +--- + +## All Commands + +### `gateway init` + +Scaffolds a `.env` file with auto-generated `JWT_SECRET`, `CREDENTIAL_ENCRYPTION_KEY`, and a SQLite `DATABASE_URL`. Use `--force` to overwrite an existing `.env`. + +```bash +gateway init --force +``` + +--- + +### `gateway start` + +Starts the Gateway server. Defaults to SQLite at `./gateway.db` on port `7521`. + +| Flag | Description | +|------|-------------| +| `-p, --port ` | Port to listen on | +| `--db ` | Database URL — overrides `DATABASE_URL` in `.env` | +| `--tunnel` | Start a Cloudflare quick tunnel and print the public URL | +| `--dashboard` | Start the built-in Next.js dashboard UI + Python agent | +| `--dashboard-path ` | Path to dashboard directory (default: `./gateway-app`) | + +```bash +# Custom port and PostgreSQL +gateway start --port 8080 --db postgres://user:pass@localhost/gateway + +# With public tunnel + dashboard +gateway start --tunnel --dashboard +``` + +When `--tunnel` and `--dashboard` are used together, the Gateway starts first, waits for the tunnel URL, then passes it to the dashboard. + +--- + +### `gateway status` + +Shows the current config and verifies the database connection — DB mode, path/URL, whether secrets are set, and user count. + +```bash +gateway status +``` + +--- + +### `gateway admin create` + +Creates an admin user in the database. + +```bash +gateway admin create --email admin@example.com --password secret --name "Alice" +``` + +| Flag | Required | Description | +|------|----------|-------------| +| `-e, --email ` | Yes | Email address | +| `-p, --password ` | Yes | Password | +| `-n, --name ` | No | Display name | + +--- + +### `gateway admin list` + +Lists all admin users. + +```bash +gateway admin list +``` + +## Next Steps + + + + + diff --git a/docs/content/docs/guides/agent-creator.mdx b/docs/content/docs/guides/agent-creator.mdx index 05d91cc..f8004b6 100644 --- a/docs/content/docs/guides/agent-creator.mdx +++ b/docs/content/docs/guides/agent-creator.mdx @@ -35,12 +35,12 @@ Register or log in to the Gateway to get access. ```bash # Register a new account -curl -X POST http://localhost:3001/api/v1/auth/register \ +curl -X POST https:///api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"email": "creator@example.com", "password": "securepass123", "name": "Agent Creator"}' # Login (returns JWT) -curl -X POST http://localhost:3001/api/v1/auth/login \ +curl -X POST https:///api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"email": "creator@example.com", "password": "securepass123"}' # Response: { "token": "eyJ...", "user": { "id": "...", ... } } @@ -70,7 +70,7 @@ Save the `token` value -- you'll need it for all admin API calls. ```bash -curl -X POST http://localhost:3001/api/v1/admin/agents \ +curl -X POST https:///api/v1/admin/agents \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -124,7 +124,7 @@ Agents operate on a **whitelist model** -- by default they cannot access any pro ```bash # Grant agent access to specific providers and tools -curl -X PUT http://localhost:3001/api/v1/admin/provider-access/agent/ \ +curl -X PUT https:///api/v1/admin/provider-access/agent/ \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -156,7 +156,7 @@ pip install simplaix-gateway \ ```bash OPENAI_API_KEY=sk-... -GATEWAY_API_URL=http://localhost:3001 +GATEWAY_API_URL=https:// AGENT_RUNTIME_TOKEN=art_yourAgentToken... ``` @@ -296,7 +296,7 @@ Use the Runtime Token when your agent needs to call back into the Gateway (e.g., ```bash # Agent calling the Unified MCP Endpoint using its Runtime Token -curl -X POST http://localhost:3001/api/v1/mcp/mcp \ +curl -X POST https:///api/v1/mcp/mcp \ -H "Authorization: Bearer art_xKz2AbCdEfGhIjKlMnOpQrStUvWxYz12345" \ -H "Content-Type: application/json" \ -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' @@ -319,7 +319,7 @@ If your token is compromised, regenerate it (invalidates the old one): ```bash -curl -X POST http://localhost:3001/api/v1/admin/agents//regenerate-token \ +curl -X POST https:///api/v1/admin/agents//regenerate-token \ -H "Authorization: Bearer " # Response: { "runtime_token": "art_newTokenHere..." } ``` @@ -374,7 +374,7 @@ The Gateway provides an MCP Proxy that lets your agent route MCP tool calls thro ```bash -curl -X POST http://localhost:3001/api/v1/admin/tool-providers \ +curl -X POST https:///api/v1/admin/tool-providers \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -493,11 +493,11 @@ Your agent can be disabled or re-enabled instantly without deletion: ```bash # Disable (all requests return 403) -curl -X POST http://localhost:3001/api/v1/admin/agents//disable \ +curl -X POST https:///api/v1/admin/agents//disable \ -H "Authorization: Bearer " # Re-enable -curl -X POST http://localhost:3001/api/v1/admin/agents//enable \ +curl -X POST https:///api/v1/admin/agents//enable \ -H "Authorization: Bearer " ``` @@ -519,7 +519,7 @@ curl -X POST http://localhost:3001/api/v1/admin/agents//enable \ ```bash -curl -X PUT http://localhost:3001/api/v1/admin/agents/ \ +curl -X PUT https:///api/v1/admin/agents/ \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -593,7 +593,7 @@ Register this agent: ```bash -curl -X POST http://localhost:3001/api/v1/admin/agents \ +curl -X POST https:///api/v1/admin/agents \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ diff --git a/docs/content/docs/guides/app-builder.mdx b/docs/content/docs/guides/app-builder.mdx index bcbd1a7..38ed7fb 100644 --- a/docs/content/docs/guides/app-builder.mdx +++ b/docs/content/docs/guides/app-builder.mdx @@ -1,122 +1,150 @@ --- -title: App Builder +title: App Builder Guide description: Build frontend applications that invoke AI agents through Simplaix Gateway. --- -This guide is for developers who **build applications** (web apps, mobile apps, chatbots) that invoke AI agents through the Simplaix Gateway. Your app authenticates end-users, invokes agents, handles credential flows, and renders streaming responses. +This guide is for developers building applications — web apps, dashboards, chatbots — that invoke AI agents through the Gateway. Your app authenticates users, invokes agents, handles credential flows, and responds to human-in-the-loop confirmations. ## Overview -As an App Builder, you will: +As an app builder you will: -1. **Authenticate** your end-users via Gateway JWT or an external identity provider -2. **Invoke agents** through the Gateway's agent invoke endpoint -3. **Handle credential flows** -- prompt users to connect services when credentials are missing -4. **Render responses** -- handle both JSON and SSE streaming responses +1. **Authenticate** users and obtain a JWT +2. **Invoke agents** via the Gateway's invoke endpoint +3. **Handle credential flows** — prompt users to connect missing services +4. **Handle confirmations** — display approval prompts for high-risk operations +5. **Render responses** — JSON or SSE streaming -## Quick Start - -### Step 1: Authenticate the User +--- -Your app needs a JWT to call the Gateway. Two options: +## Step 1: Authenticate the User -**Option A: Gateway-issued JWT** (simplest for prototyping) +### Option A: Gateway-issued JWT ```typescript -// Login to get a JWT -const res = await fetch('http://localhost:3001/api/v1/auth/login', { +const res = await fetch('https:///api/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - email: 'user@example.com', - password: 'password123', - }), + body: JSON.stringify({ email: 'user@example.com', password: 'password' }), }); -const { token } = await res.json(); +const { token, user } = await res.json(); // Store token for subsequent requests ``` -**Option B: External Identity Provider** (recommended for production) +To refresh a token: + +```typescript +const res = await fetch('https:///api/v1/auth/refresh', { + method: 'POST', + headers: { Authorization: `Bearer ${token}` }, +}); +const { token: newToken } = await res.json(); +``` + +### Option B: External Identity Provider (recommended for production) -Configure your IdP (Auth0, Azure AD, Okta, etc.) in the Gateway's `.env`: +Configure your IdP in the Gateway's `.env` and pass your IdP-issued JWT directly: ```bash +# Auth0 / OIDC JWT_EXTERNAL_ISSUERS='[{ "issuer": "https://your-tenant.auth0.com/", "jwksUri": "https://your-tenant.auth0.com/.well-known/jwks.json", "audience": "simplaix-gateway" }]' + +# Azure AD +JWT_EXTERNAL_ISSUERS='[{ + "issuer": "https://login.microsoftonline.com/TENANT/v2.0", + "jwksUri": "https://login.microsoftonline.com/TENANT/discovery/v2.0/keys", + "audience": "api://simplaix-gateway" +}]' ``` -Then pass your IdP-issued JWT directly to the Gateway. +### Authentication methods -### Step 2: Pre-check Credentials +The Gateway's flexible auth middleware accepts tokens in three ways: -Before invoking an agent, check if the user has all required credentials: +| Method | Format | +|--------|--------| +| Authorization header | `Authorization: Bearer ` | +| Query param (JWT) | `?_token=` | +| Query param (API key) | `?_api_key=gk_xxx` | -```typescript -const agentId = 'abc123'; +Query params are useful for CopilotKit's `HttpAgent` which cannot set custom headers. -const check = await fetch( - `http://localhost:3001/api/v1/agents/${agentId}/credentials-check`, - { headers: { Authorization: `Bearer ${token}` } } -); +--- -const result = await check.json(); +## Step 2: Invoke an Agent -if (result.code === 'CREDENTIALS_REQUIRED') { - // Show "Connect" buttons for missing services - for (const [service, url] of Object.entries(result.authUrls)) { - console.log(`Connect ${service}: ${url}`); - } -} else { - console.log('All credentials available, ready to invoke!'); -} +```typescript +const res = await fetch(`https:///api/v1/agents/${agentId}/invoke`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + messages: [{ role: 'user', content: 'Show me my recent transactions' }], + }), +}); ``` -### Step 3: Invoke the Agent +The request body is forwarded as-is to the agent runtime — structure it however your agent expects. -```typescript -const response = await fetch( - `http://localhost:3001/api/v1/agents/${agentId}/invoke`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - message: 'Show me my recent transactions', - }), - } -); +### Handle the response + +Agents can respond with JSON or an SSE stream: -// Check for credential requirements -if (response.status === 401) { - const error = await response.json(); +```typescript +if (res.status === 401) { + const error = await res.json(); if (error.code === 'CREDENTIALS_REQUIRED') { - // Redirect user to connect missing services + // Prompt user to connect missing services (see Credential Flow below) showAuthPrompt(error.missing, error.authUrls); return; } } -// Handle the response -const contentType = response.headers.get('Content-Type') || ''; +const contentType = res.headers.get('Content-Type') ?? ''; if (contentType.includes('text/event-stream')) { - // Handle SSE streaming response - await handleSSEStream(response); + await handleSSEStream(res); } else { - // Handle JSON response - const data = await response.json(); + const { data } = await res.json(); console.log(data); } ``` -## Handling SSE Streaming +#### Error codes + +| Code | Meaning | +|------|---------| +| `CREDENTIALS_REQUIRED` | One or more credentials are missing for this agent | +| `AGENT_NOT_FOUND` | No agent with that ID exists | +| `AGENT_DISABLED` | Agent has been disabled (kill switch) | +| `TENANT_MISMATCH` | Agent belongs to a different tenant | +| `RUNTIME_ERROR` | The upstream agent runtime returned an error | + +### Pre-check credentials (optional) + +Before invoking, you can check whether all credentials are in place: + +```typescript +const res = await fetch( + `https:///api/v1/agents/${agentId}/credentials-check`, + { headers: { Authorization: `Bearer ${token}` } }, +); + +if (res.status === 401) { + const { missing, authUrls } = await res.json(); + // Show "Connect" buttons before proceeding +} +``` + +--- -Many agents return SSE (Server-Sent Events) for real-time streaming. The Gateway transparently forwards these streams. +## Step 3: Handle SSE Streaming ```typescript async function handleSSEStream(response: Response) { @@ -130,204 +158,206 @@ async function handleSSEStream(response: Response) { buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); - buffer = lines.pop() || ''; + buffer = lines.pop() ?? ''; for (const line of lines) { - if (line.startsWith('data: ')) { - const data = line.slice(6); - if (data === '[DONE]') return; - - try { - const event = JSON.parse(data); - // Process the event (depends on agent protocol) - handleAgentEvent(event); - } catch { - // Non-JSON SSE data - console.log('Agent:', data); - } + if (!line.startsWith('data: ')) continue; + const data = line.slice(6); + if (data === '[DONE]') return; + try { + const event = JSON.parse(data); + handleAgentEvent(event); + } catch { + console.log('Agent:', data); } } } } ``` -## Credential Flow +--- -When an agent requires credentials the user hasn't connected yet, the Gateway returns a `401` with details: +## Step 4: Credential Flow + +When credentials are missing, the Gateway returns a `401` with this shape: ```json { "code": "CREDENTIALS_REQUIRED", - "missing": ["stripe_api", "slack"], + "missing": ["github", "slack"], "authUrls": { - "stripe_api": "/auth/connect?service=stripe_api", - "slack": "/auth/connect?service=slack" + "github": "https://gateway.example.com/auth/connect?service=github", + "slack": "https://gateway.example.com/auth/connect?service=slack" }, - "message": "Authentication required for: stripe_api, slack" + "message": "Authentication required for: github, slack" } ``` -### Implementing the Auth Flow +Direct the user to the `authUrls` for each missing service (OAuth redirect or popup). Once they complete the flow, retry the agent invocation. -1. **Show a prompt** to the user with "Connect" buttons for each missing service -2. **Open the auth URL** (e.g., in a popup or redirect) -- this is the `connectUrl` configured by the admin on the credential provider -3. **Store the credential** by calling the Gateway's credential endpoint -4. **Retry** the agent invocation +### Storing a manual credential (API token or JWT) -**Example: Storing a JWT credential (e.g., user's API token)** +For services that use API keys rather than OAuth: ```typescript -// User enters their API token in a form -async function connectService(serviceType: string, apiToken: string) { - // Look up the credential provider - const providerRes = await fetch( - `http://localhost:3001/api/v1/credential-providers/by-service/${serviceType}`, - { headers: { Authorization: `Bearer ${token}` } } - ); - const { provider } = await providerRes.json(); +// 1. Look up the credential provider +const providerRes = await fetch( + `https:///api/v1/credential-providers/by-service/${serviceType}`, + { headers: { Authorization: `Bearer ${token}` } }, +); +const { provider } = await providerRes.json(); + +// 2. Store the credential +await fetch('https:///api/v1/credentials/jwt', { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ providerId: provider.id, token: apiToken }), +}); + +// 3. Retry agent invocation +``` + +For API key credentials: + +```typescript +await fetch('https:///api/v1/credentials/apikey', { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ providerId: provider.id, apiKey: keyValue }), +}); +``` + +### List and delete credentials + +```typescript +// List +const { credentials } = await fetch('https:///api/v1/credentials', { + headers: { Authorization: `Bearer ${token}` }, +}).then((r) => r.json()); + +// Delete +await fetch(`https:///api/v1/credentials/${credentialId}`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${token}` }, +}); +``` - // Store the credential - await fetch('http://localhost:3001/api/v1/credentials/jwt', { +--- + +## Step 5: Human-in-the-Loop Confirmations + +When an agent triggers a tool that requires confirmation, the Gateway pauses execution and emits an SSE event. Your app listens on the confirmation stream and responds. + +### Listen for confirmations + +```typescript +// EventSource doesn't support custom headers — use _token query param +const stream = new EventSource( + `https:///api/v1/stream?_token=${token}` +); + +stream.addEventListener('CONFIRMATION_REQUIRED', (event) => { + const req = JSON.parse(event.data); + // { + // id: "conf_123", + // tool: { name: "delete_file", description: "...", provider: { id, name } }, + // arguments: { path: "/report.pdf" }, + // risk: { level: "high" }, + // agent: { id: "agent_1", name: "File Manager" }, + // timestamp: "2025-01-15T10:30:00Z" + // } + showConfirmationDialog(req); +}); + +stream.addEventListener('CONFIRMATION_RESOLVED', (event) => { + const { id, confirmed } = JSON.parse(event.data); + dismissConfirmationDialog(id); +}); + +// 30-second heartbeat keeps the connection alive +stream.addEventListener('heartbeat', () => {}); +``` + +### Confirm or reject + +```typescript +async function respond(confirmationId: string, approved: boolean, reason?: string) { + const action = approved ? 'confirm' : 'reject'; + await fetch(`https:///api/v1/confirmations/${confirmationId}/${action}`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, - body: JSON.stringify({ - providerId: provider.id, - token: apiToken, - }), + body: JSON.stringify({ reason }), }); - - // Credential stored! Now retry the agent invocation. } ``` -## CopilotKit Integration +### Poll for pending confirmations (non-SSE fallback) -The Gateway integrates with [CopilotKit](https://copilotkit.ai/) for building AI-powered chat interfaces. The `gateway-app` demonstrates a full CopilotKit + AG-UI integration. +```typescript +const { confirmations } = await fetch( + 'https:///api/v1/confirmations/list?status=pending', + { headers: { Authorization: `Bearer ${token}` } }, +).then((r) => r.json()); +``` -### Setup +--- -1. **Configure the CopilotKit runtime** to proxy through the Gateway: +## CopilotKit Integration -```typescript -// gateway-app/src/app/api/copilotkit/route.ts -import { - CopilotRuntime, - ExperimentalEmptyAdapter, - copilotRuntimeNextJSAppRouterEndpoint, -} from '@copilotkit/runtime'; +The Gateway works natively with [CopilotKit](https://copilotkit.ai/). Point the CopilotKit runtime at the Gateway's invoke endpoint: -const AGENT_URL = process.env.AGENT_URL || 'http://localhost:8000'; -const GATEWAY_URL = process.env.NEXT_PUBLIC_GATEWAY_URL || 'http://localhost:3001'; +```typescript +// app/api/copilotkit/route.ts +import { CopilotRuntime, ExperimentalEmptyAdapter, copilotRuntimeNextJSAppRouterEndpoint } from '@copilotkit/runtime'; -// The CopilotKit runtime invokes agents through the Gateway const runtime = new CopilotRuntime({ remoteEndpoints: [ { - url: `${GATEWAY_URL}/api/v1/agents//invoke`, + // Use _token query param since CopilotKit's HttpAgent cannot set headers + url: `${process.env.GATEWAY_API_URL}/api/v1/agents/${agentId}/invoke?_token=${userToken}`, }, ], }); -``` -2. **Use the CopilotKit provider** in your React app: +export const POST = copilotRuntimeNextJSAppRouterEndpoint({ + runtime, + serviceAdapter: new ExperimentalEmptyAdapter(), + endpoint: '/api/copilotkit', +}).POST; +``` ```tsx +// In your React app import { CopilotKit } from '@copilotkit/react-core'; import { CopilotChat } from '@copilotkit/react-ui'; -function App() { +export function Chat() { return ( - + ); } ``` -3. **Handle auth** by passing the JWT via query parameters (for CopilotKit's HttpAgent which cannot set custom headers): - -```typescript -// The Gateway's flexibleAuthMiddleware accepts _token and _api_key query params -const agentUrl = `${GATEWAY_URL}/api/v1/agents/${agentId}/invoke?_token=${jwt}`; -``` - -## Confirmation Flow (Human-in-the-Loop) - -If the agent triggers a tool that requires confirmation, the Gateway pauses the request and emits an SSE event. Your app can listen for these: - -### Listening for Confirmation Requests - -```typescript -// Connect to SSE stream -const eventSource = new EventSource( - `http://localhost:3001/api/v1/stream`, - // Note: EventSource doesn't support custom headers, - // use a polyfill or fetch-based SSE for JWT auth -); - -eventSource.addEventListener('CONFIRMATION_REQUIRED', (event) => { - const confirmation = JSON.parse(event.data); - // Show confirmation UI - showConfirmationCard({ - id: confirmation.id, - toolName: confirmation.action, - arguments: confirmation.params, - risk: confirmation.risk, - }); -}); -``` - -### Responding to Confirmations - -```typescript -// Confirm -await fetch(`http://localhost:3001/api/v1/confirmation/${confirmationId}/confirm`, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ reason: 'Looks good' }), -}); - -// Or reject -await fetch(`http://localhost:3001/api/v1/confirmation/${confirmationId}/reject`, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ reason: 'Too risky' }), -}); -``` - -### Polling Fallback - -If SSE is not feasible, poll for pending confirmations: - -```typescript -const res = await fetch('http://localhost:3001/api/v1/stream/pending', { - headers: { Authorization: `Bearer ${token}` }, -}); -const { pending } = await res.json(); -``` +--- -## Using the MCP Proxy from Frontend +## MCP Proxy -If your app directly communicates with MCP servers (e.g., for a tool marketplace), route through the Gateway's MCP Proxy: +To call MCP servers directly from your app, route through the Gateway's MCP proxy — it handles auth, policy enforcement, and audit logging transparently: ```typescript -const providerId = 'slack-provider-id'; - -// Standard MCP Streamable HTTP -- just point to the Gateway proxy -const response = await fetch( - `http://localhost:3001/api/v1/mcp-proxy/${providerId}/mcp`, +const res = await fetch( + `https:///api/v1/mcp-proxy/${providerId}/mcp`, { method: 'POST', headers: { @@ -335,128 +365,31 @@ const response = await fetch( 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream', }, - body: JSON.stringify({ - jsonrpc: '2.0', - method: 'tools/list', - id: 1, - }), - } + body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/list', id: 1 }), + }, ); ``` -The proxy handles authentication, ACL, policy enforcement, and audit logging transparently. - -## Example: Complete React Chat App - -```tsx -import { useState } from 'react'; - -function ChatApp({ agentId, token }: { agentId: string; token: string }) { - const [messages, setMessages] = useState([]); - const [input, setInput] = useState(''); - const [loading, setLoading] = useState(false); - const [authPrompt, setAuthPrompt] = useState<{ - missing: string[]; - authUrls: Record; - } | null>(null); - - async function sendMessage() { - if (!input.trim()) return; - setLoading(true); - setMessages((prev) => [...prev, `You: ${input}`]); - - try { - const res = await fetch( - `http://localhost:3001/api/v1/agents/${agentId}/invoke`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ message: input }), - } - ); - - if (res.status === 401) { - const error = await res.json(); - if (error.code === 'CREDENTIALS_REQUIRED') { - setAuthPrompt({ missing: error.missing, authUrls: error.authUrls }); - return; - } - } - - const contentType = res.headers.get('Content-Type') || ''; - if (contentType.includes('text/event-stream')) { - // Handle streaming - const reader = res.body!.getReader(); - const decoder = new TextDecoder(); - let agentMsg = ''; - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - agentMsg += decoder.decode(value, { stream: true }); - } - setMessages((prev) => [...prev, `Agent: ${agentMsg}`]); - } else { - const data = await res.json(); - setMessages((prev) => [...prev, `Agent: ${JSON.stringify(data.data)}`]); - } - } catch (err) { - setMessages((prev) => [...prev, `Error: ${err}`]); - } finally { - setLoading(false); - setInput(''); - } - } - - return ( -
- {authPrompt && ( -
-

Please connect the following services:

- {authPrompt.missing.map((service) => ( - - Connect {service} - - ))} -
- )} -
- {messages.map((msg, i) => ( -
{msg}
- ))} -
- setInput(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && sendMessage()} - disabled={loading} - /> - -
- ); -} -``` +--- -## API Reference Quick List +## API Reference | Endpoint | Method | Description | |----------|--------|-------------| -| `/api/v1/auth/login` | POST | Get a JWT | -| `/api/v1/auth/register` | POST | Create account | +| `/api/v1/auth/login` | POST | Login — returns JWT + user | +| `/api/v1/auth/register` | POST | Register a new user | +| `/api/v1/auth/refresh` | POST | Refresh JWT | | `/api/v1/agents/:id/invoke` | POST | Invoke an agent | | `/api/v1/agents/:id/credentials-check` | GET | Pre-check credentials | | `/api/v1/agents/:id` | GET | Get agent info | | `/api/v1/credentials` | GET | List user credentials | -| `/api/v1/credentials/jwt` | POST | Store JWT credential | +| `/api/v1/credentials/jwt` | POST | Store JWT / token credential | | `/api/v1/credentials/apikey` | POST | Store API key credential | +| `/api/v1/credentials/:id` | DELETE | Delete a credential | | `/api/v1/credential-providers` | GET | List credential providers | -| `/api/v1/stream` | GET | SSE event stream | -| `/api/v1/stream/pending` | GET | Pending confirmations | -| `/api/v1/confirmation/:id/confirm` | POST | Confirm a request | -| `/api/v1/confirmation/:id/reject` | POST | Reject a request | +| `/api/v1/credential-providers/by-service/:type` | GET | Look up provider by service type | +| `/api/v1/stream` | GET | SSE confirmation stream | +| `/api/v1/confirmations/list` | GET | List confirmations (poll fallback) | +| `/api/v1/confirmations/:id/confirm` | POST | Confirm a request | +| `/api/v1/confirmations/:id/reject` | POST | Reject a request | | `/api/v1/mcp-proxy/:providerId/mcp` | POST | MCP proxy (Streamable HTTP) | diff --git a/docs/content/docs/guides/gateway-admin.mdx b/docs/content/docs/guides/gateway-admin.mdx index 8a6660c..e3475af 100644 --- a/docs/content/docs/guides/gateway-admin.mdx +++ b/docs/content/docs/guides/gateway-admin.mdx @@ -1,314 +1,290 @@ --- -title: Gateway Admin +title: Gateway Admin Guide description: Deploy, configure, and manage a Simplaix Gateway instance. --- -This guide is for **platform administrators** who deploy and operate a Simplaix Gateway instance. You'll learn how to configure authentication, manage users, set up tool providers, define policies, control access, and monitor the system. +This guide is for **platform administrators** who deploy and operate a Simplaix Gateway instance. You'll learn how to configure authentication, manage users and agents, define access policies, set up credential providers, and monitor the system. ## Overview -As a Gateway Admin, you are responsible for: +As a Gateway Admin you are responsible for: -1. **Deploying** the Gateway (local, Vercel, or custom infrastructure) -2. **Configuring** authentication, database, encryption, and external identity providers -3. **Managing users** and roles -4. **Registering tool providers** and configuring access control -5. **Defining policies** for tool-call governance -6. **Setting up credential providers** for the credential vault -7. **Monitoring** via audit logs and health checks +1. **Deploying** the Gateway and configuring environment variables +2. **Managing users** and assigning roles +3. **Registering agents** and issuing runtime tokens +4. **Configuring tool providers** (MCP servers) and access policies +5. **Setting up credential providers** for the credential vault +6. **Monitoring** via audit logs -## Quick Start - -### Step 1: Install and Configure - -```bash -# Clone the repository -git clone https://github.com/simplaix/simplaix-gateway.git -cd simplaix-gateway - -# Install dependencies -pnpm install - -# Copy environment template -cp .env.example .env -``` - -Edit `.env` with your settings: - -```bash -# ===== JWT Configuration ===== -JWT_SECRET=generate-a-strong-random-secret-here -JWT_ISSUER=simplaix-gateway -JWT_EXPIRES_IN=24h - -# ===== Initial Admin User ===== -ADMIN_EMAIL=admin@yourcompany.com -ADMIN_PASSWORD=a-strong-admin-password - -# ===== Database ===== -# SQLite (default, zero setup — uses absolute path so all commands share one DB) -DATABASE_URL=file:~/.simplaix-gateway/data/gateway.db -# PostgreSQL: DATABASE_URL=postgres://user:pass@host/db - -# ===== Credential Encryption ===== -# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -CREDENTIAL_ENCRYPTION_KEY=your-64-character-hex-string - -# ===== Default MCP Server ===== -MCP_SERVER_URL=http://localhost:8080 -``` - -### Step 2: Start the Gateway - -```bash -# Development mode -pnpm dev +--- -# The Gateway starts on http://localhost:3001 -``` +## Quick Start -### Step 3: Verify +Install the Gateway CLI globally and scaffold a config: ```bash -# Health check -curl http://localhost:3001/api/health - -# Login as admin -curl -X POST http://localhost:3001/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email": "admin@yourcompany.com", "password": "a-strong-admin-password"}' +npm install -g @simplaix/simplaix-gateway +gateway init +gateway start +gateway admin create --email admin@example.com --password changeme ``` -### Step 4: Start the Dashboard (optional) +The Gateway starts on port `7521` by default. See [Deployment](/docs/getting-started/deployment) for production deployment options. -```bash -cd gateway-app -pnpm install -pnpm dev -# Dashboard available at http://localhost:3000 -``` - -## Configuration Reference +--- -### Environment Variables +## Environment Variables | Variable | Required | Default | Description | |----------|----------|---------|-------------| -| `JWT_SECRET` | Yes | - | Secret for signing gateway JWTs (HMAC) | -| `JWT_PUBLIC_KEY` | No | - | PEM public key for asymmetric JWT (RSA/EC) | -| `JWT_ISSUER` | No | `simplaix-gateway` | JWT issuer claim | -| `JWT_AUDIENCE` | No | - | JWT audience claim | -| `JWT_EXPIRES_IN` | No | `24h` | Token expiration (`1h`, `7d`, etc.) | -| `JWT_EXTERNAL_ISSUERS` | No | - | JSON array of external IdP configs | -| `ADMIN_EMAIL` | No | - | Initial admin email (created on first boot) | -| `ADMIN_PASSWORD` | No | - | Initial admin password | -| `DATABASE_URL` | No | `file:~/.simplaix-gateway/data/gateway.db` | SQLite `file:` path or PostgreSQL `postgres://` URL | -| `CREDENTIAL_ENCRYPTION_KEY` | No | - | 64-char hex key for AES-256-GCM | +| `JWT_SECRET` | Yes | — | Secret for signing/verifying JWTs (min 32 chars) | +| `JWT_PUBLIC_KEY` | No | — | PEM public key for asymmetric JWT (RSA/EC) | +| `JWT_ISSUER` | No | `simplaix-gateway` | JWT `iss` claim | +| `JWT_AUDIENCE` | No | — | JWT `aud` claim | +| `JWT_EXPIRES_IN` | No | `24h` | Token expiration | +| `JWT_EXTERNAL_ISSUERS` | No | — | JSON array of external IdP configs | +| `ADMIN_EMAIL` | No | — | Auto-created admin on first boot | +| `ADMIN_PASSWORD` | No | — | Password for auto-created admin | +| `DATABASE_URL` | No | SQLite `~/.simplaix-gateway/data/gateway.db` | SQLite `file:` path or `postgres://` URL | +| `CREDENTIAL_ENCRYPTION_KEY` | Yes | — | 64-char hex key for AES-256-GCM | +| `PORT` | No | `7521` | HTTP port | +| `GATEWAY_PUBLIC_URL` | No | — | Public base URL for OAuth callbacks and pairing links | +| `OAUTH_CALLBACK_BASE_URL` | No | — | Override OAuth callback base separately | | `MCP_SERVER_URL` | No | `http://localhost:8080` | Default MCP server URL | -| `OAUTH_CALLBACK_BASE_URL` | No | - | Base URL for OAuth callbacks | - -### External Identity Providers - -To accept JWTs from external IdPs (Auth0, Azure AD, Okta, etc.): -```bash -JWT_EXTERNAL_ISSUERS='[ - { - "issuer": "https://your-tenant.auth0.com/", - "jwksUri": "https://your-tenant.auth0.com/.well-known/jwks.json", - "audience": "simplaix-gateway" - }, - { - "issuer": "https://login.microsoftonline.com/TENANT_ID/v2.0", - "jwksUri": "https://login.microsoftonline.com/TENANT_ID/discovery/v2.0/keys", - "audience": "api://simplaix-gateway" - } -]' -``` - -Each issuer config supports: - -| Field | Description | -|-------|-------------| -| `issuer` | JWT `iss` claim to match | -| `secret` | Shared secret for HMAC (HS256) validation | -| `jwksUri` | JWKS endpoint URL for RSA/EC validation | -| `audience` | Expected `aud` claim | +### External identity providers -### Database - -**SQLite** (development, single-instance): +To accept JWTs from Auth0, Azure AD, Okta, etc.: ```bash -DATABASE_URL=file:~/.simplaix-gateway/data/gateway.db -``` - -**PostgreSQL** (production, multi-instance): +# OIDC / JWKS +JWT_EXTERNAL_ISSUERS='[{ + "issuer": "https://your-tenant.auth0.com/", + "jwksUri": "https://your-tenant.auth0.com/.well-known/jwks.json", + "audience": "simplaix-gateway" +}]' -```bash -DATABASE_URL=postgres://user:password@host:5432/gateway +# Shared secret (HS256) +JWT_EXTERNAL_ISSUERS='[{ + "issuer": "https://auth.example.com", + "secret": "shared-secret", + "audience": "simplaix-gateway" +}]' ``` -Run migrations: - -```bash -pnpm db:generate -pnpm db:migrate -``` +--- ## User Management ### Roles -The Gateway has three built-in roles: - | Role | Permissions | |------|-------------| -| `admin` | Full access: manage users, agents, providers, view all audit logs | -| `tenant_admin` | Manage agents and providers within own tenant, view tenant audit logs | +| `admin` | Full access — manage all users, agents, providers, audit logs across all tenants | +| `tenant_admin` | Admin within own tenant — manage agents, providers, policies, view tenant audit logs | | `agent_creator` | Create and manage own agents, view own audit logs | -### Create Users +### Create a user ```bash -# Create a user (admin only) -curl -X POST http://localhost:3001/api/v1/admin/users \ +curl -X POST https:///api/v1/admin/users \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ - "email": "creator@company.com", + "email": "dev@example.com", "password": "securepass123", - "name": "Agent Developer", + "name": "Alice", "tenantId": "tenant-acme", "roles": ["agent_creator"] }' ``` -### Manage Roles +`roles` defaults to `["agent_creator"]` if omitted. + +### Manage roles ```bash # Assign a role -curl -X POST http://localhost:3001/api/v1/admin/users//roles \ +curl -X POST https:///api/v1/admin/users//roles \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"role": "tenant_admin"}' # Remove a role -curl -X DELETE http://localhost:3001/api/v1/admin/users//roles/tenant_admin \ +curl -X DELETE https:///api/v1/admin/users//roles/tenant_admin \ -H "Authorization: Bearer " ``` -### List Users +### List and delete users ```bash -curl http://localhost:3001/api/v1/admin/users \ +# List all users +curl https:///api/v1/admin/users \ + -H "Authorization: Bearer " + +# Delete a user +curl -X DELETE https:///api/v1/admin/users/ \ -H "Authorization: Bearer " ``` +--- + +## Agent Management + +### Register an agent + +```bash +curl -X POST https:///api/v1/admin/agents \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Support Agent", + "upstreamUrl": "http://support-agent:8000", + "description": "Handles customer inquiries", + "tenantId": "tenant-acme" + }' +``` + +**Request fields:** + +| Field | Required | Description | +|-------|----------|-------------| +| `name` | Yes | Display name | +| `upstreamUrl` | Yes | URL of the agent runtime | +| `upstreamSecret` | No | Bearer token sent to the upstream runtime | +| `description` | No | Human-readable description | +| `requireConfirmation` | No | Require confirmation for all tool calls (default: `false`) | +| `requiredCredentials` | No | Credential requirements for the agent | +| `tenantId` | No | Tenant scope | + +The response includes a `runtime_token` (`art_...`) — **store it immediately, it is shown only once**. + +```json +{ + "success": true, + "agent": { + "id": "abc123", + "name": "Support Agent", + "upstreamUrl": "http://support-agent:8000", + "isActive": true, + "requireConfirmation": false, + "runtimeTokenPrefix": "art_xxxx", + "createdAt": "2025-01-15T10:00:00Z" + }, + "runtime_token": "art_xxxxxxxxxxxxxxxx..." +} +``` + +### Kill switch + +```bash +# Disable — all invocations immediately return 403 +curl -X POST https:///api/v1/admin/agents//disable \ + -H "Authorization: Bearer " + +# Re-enable +curl -X POST https:///api/v1/admin/agents//enable \ + -H "Authorization: Bearer " +``` + +### Regenerate runtime token + +```bash +curl -X POST https:///api/v1/admin/agents//regenerate-token \ + -H "Authorization: Bearer " +# Returns a new runtime_token — the old one is immediately invalidated +``` + +--- + ## API Key Management -API Keys (`gk_` prefix) provide server-to-server trust for agent runtimes calling back into the Gateway. +API keys (`gk_` prefix) provide server-to-server authentication for agent runtimes or external services calling the Gateway. -### Create an API Key +### Create an API key ```bash -curl -X POST http://localhost:3001/api/v1/admin/api-keys \ +curl -X POST https:///api/v1/admin/api-keys \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "Production Agent Server", - "scopes": ["credentials:resolve", "credentials:read"] + "scopes": ["credentials:resolve"] }' -# Response: { "key": "gk_xxxxxxxxxxx...", ... } -# ⚠️ The key is shown only once! ``` +The full key is returned once in the response — store it securely. + ### Scopes | Scope | Description | |-------|-------------| -| `credentials:resolve` | Resolve user credentials (default) | +| `credentials:resolve` | Resolve user credentials for injection (default) | | `credentials:read` | Read credential metadata | | `credentials:write` | Store/update credentials on behalf of users | -### List and Revoke +### List and revoke ```bash -# List keys -curl http://localhost:3001/api/v1/admin/api-keys \ +# List (shows keyPrefix, not the full key) +curl https:///api/v1/admin/api-keys \ -H "Authorization: Bearer " -# Revoke a key -curl -X DELETE http://localhost:3001/api/v1/admin/api-keys/ \ +# Revoke +curl -X DELETE https:///api/v1/admin/api-keys/ \ -H "Authorization: Bearer " ``` +--- + ## Tool Provider Management -Tool Providers map tool name patterns to upstream MCP server endpoints. They control where tool calls are routed. +Tool Providers map glob patterns to upstream MCP server endpoints — they define where tool calls are routed. -### Create a Tool Provider +### Create a tool provider ```bash -curl -X POST http://localhost:3001/api/v1/admin/tool-providers \ +curl -X POST https:///api/v1/admin/tool-providers \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ - "name": "Slack Integration", + "name": "Slack MCP", "pattern": "slack_*", - "endpoint": "http://slack-mcp-server:3000", + "endpoint": "http://slack-mcp:3000", "authType": "bearer", - "authSecret": "upstream-mcp-secret", - "priority": 10, - "description": "Routes slack_* tool calls to the Slack MCP server" + "authSecret": "upstream-secret", + "priority": 10 }' ``` -### Pattern Matching - -Tool providers use glob patterns to match tool names: - -| Pattern | Matches | -|---------|---------| -| `slack_*` | `slack_send_message`, `slack_list_channels` | -| `github_*` | `github_create_issue`, `github_list_repos` | -| `*` | All tools (catch-all, use with low priority) | - -Higher `priority` values match first. +| Field | Required | Description | +|-------|----------|-------------| +| `name` | Yes | Display name | +| `pattern` | Yes | Glob pattern matching tool names (e.g., `slack_*`, `*`) | +| `endpoint` | Yes | MCP server URL | +| `authType` | No | `bearer`, `api_key`, or `none` (default) | +| `authSecret` | No | Token sent to upstream if `authType` is set | +| `priority` | No | Higher value = checked first when multiple patterns match | +| `description` | No | Description | -### Authentication Types - -| Auth Type | Description | -|-----------|-------------| -| `none` | No authentication to upstream | -| `bearer` | `Authorization: Bearer ` | -| `api_key` | `X-API-Key: ` | - -### CRUD Operations +### Fetch tools from a provider ```bash -# List providers -GET /api/v1/admin/tool-providers - -# Get a specific provider -GET /api/v1/admin/tool-providers/:id - -# Update a provider -PUT /api/v1/admin/tool-providers/:id - -# Delete a provider -DELETE /api/v1/admin/tool-providers/:id +curl https:///api/v1/admin/tool-providers//tools \ + -H "Authorization: Bearer " ``` -## Provider Access Control (ACL) +--- + +## Provider Access Control -ACL rules control which users and roles can access which tool providers through the MCP Proxy. +Access rules control which users and agents can invoke which tools on which providers. -### Create Access Rules +### Create an access rule ```bash # Allow a specific user to access a provider -curl -X POST http://localhost:3001/api/v1/admin/provider-access \ +curl -X POST https:///api/v1/admin/provider-access \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -318,19 +294,21 @@ curl -X POST http://localhost:3001/api/v1/admin/provider-access \ "action": "allow" }' -# Allow an agent to access all providers -curl -X POST http://localhost:3001/api/v1/admin/provider-access \ +# Require confirmation for dangerous tools +curl -X POST https:///api/v1/admin/provider-access \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "subjectType": "agent", - "subjectId": "agent-abc123", - "providerId": "*", - "action": "allow" + "subjectId": "agent-abc", + "providerId": "files-provider-id", + "action": "require_confirmation", + "toolPattern": "delete_*", + "riskLevel": "critical" }' -# Deny a specific user from a sensitive provider -curl -X POST http://localhost:3001/api/v1/admin/provider-access \ +# Deny a user from a sensitive provider +curl -X POST https:///api/v1/admin/provider-access \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ @@ -341,354 +319,181 @@ curl -X POST http://localhost:3001/api/v1/admin/provider-access \ }' ``` -### Subject Types +**Rule fields:** -| Type | Description | `subjectId` example | -|------|-------------|---------------------| -| `user` | Specific user | User ID (`usr_abc123`) | -| `agent` | A registered agent | Agent ID (`agent-abc123`) | +| Field | Required | Description | +|-------|----------|-------------| +| `subjectType` | Yes | `user` or `agent` | +| `subjectId` | Yes | User ID or agent ID | +| `providerId` | Yes | Tool provider ID, or `*` for all providers | +| `action` | Yes | `allow`, `deny`, or `require_confirmation` | +| `toolPattern` | No | Glob pattern for tool names (default: `*`) | +| `riskLevel` | No | `low`, `medium`, `high`, or `critical` | -### Evaluation Order +### Set agent rules atomically -1. **User-level deny** rules are checked first -2. **User-level allow** rules are checked next -3. **Agent-level deny** rules -4. **Agent-level allow** rules -5. **Wildcard** provider rules (deny, then allow) -6. If no rules match, access is **denied by default** (whitelist model) - -### List and Delete Rules +Replace all access rules for an agent in one request: ```bash -# List all rules -GET /api/v1/admin/provider-access - -# Get a specific rule -GET /api/v1/admin/provider-access/:id - -# Delete a rule -DELETE /api/v1/admin/provider-access/:id +curl -X PUT https:///api/v1/admin/provider-access/agent/ \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "rules": [ + { "providerId": "slack-provider-id", "action": "allow" }, + { "providerId": "files-provider-id", "action": "require_confirmation", "toolPattern": "delete_*", "riskLevel": "critical" } + ] + }' ``` -## Policy Engine +### Policy evaluation -Policies control what happens when a tool is called. They are configured in `src/config.ts`. +Test a policy before deploying: -### Default Policies - -```typescript -const policies = [ - { tool: 'transfer_money', action: 'require_confirmation', risk: 'high' }, - { tool: 'delete_*', action: 'require_confirmation', risk: 'critical' }, - { tool: 'write_*', action: 'require_confirmation', risk: 'medium' }, - { tool: 'read_*', action: 'allow', risk: 'low' }, - { tool: '*', action: 'allow', risk: 'low' }, -]; +```bash +curl -X POST https:///api/v1/admin/provider-access/evaluate \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "userId": "user-123", + "providerId": "slack-provider-id", + "toolName": "slack_send_message", + "agentId": "agent-abc" + }' +# Returns: { "action": "allow|deny|require_confirmation", "risk": "low|...", "matchedRule": {...} } ``` -### Policy Actions - -| Action | Behavior | -|--------|----------| -| `allow` | Proceeds immediately | -| `deny` | Blocked with 403 Forbidden | -| `require_confirmation` | Pauses until a human confirms via SSE | +### Evaluation order -### Risk Levels +1. User-level **deny** rules +2. User-level **allow** rules +3. Agent-level **deny** rules +4. Agent-level **allow** rules +5. Wildcard provider rules (deny, then allow) +6. No match → **denied by default** -| Level | Description | -|-------|-------------| -| `low` | Read-only operations | -| `medium` | Write operations | -| `high` | Financial or sensitive operations | -| `critical` | Destructive operations (delete, etc.) | +--- ## Credential Provider Setup -Credential Providers define how each external service credential type works. - -### Create a Credential Provider - -**JWT-based (e.g., Gateway API access):** - -```bash -curl -X POST http://localhost:3001/api/v1/credential-providers \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Gateway API", - "serviceType": "gateway_api", - "authType": "jwt", - "description": "Access to Gateway management APIs", - "config": { - "connectUrl": "/auth/connect?service=gateway_api", - "jwt": { - "headerName": "Authorization", - "prefix": "Bearer " - } - } - }' -``` +Credential Providers define how each service credential type works. Users store credentials against these providers; agents receive them as injected headers. -**API Key-based (e.g., Stripe):** +### `api_key` provider ```bash -curl -X POST http://localhost:3001/api/v1/credential-providers \ +curl -X POST https:///api/v1/credential-providers \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "Stripe", - "serviceType": "stripe_api", + "serviceType": "stripe", "authType": "api_key", - "description": "Stripe payment API access", - "config": { - "connectUrl": "/auth/connect?service=stripe_api", - "apiKey": { - "headerName": "Authorization", - "prefix": "Bearer " - } - } + "description": "Stripe payment API" }' ``` -**OAuth2 (e.g., Google):** +### `oauth2` provider ```bash -curl -X POST http://localhost:3001/api/v1/credential-providers \ +curl -X POST https:///api/v1/credential-providers \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "Google", "serviceType": "google", "authType": "oauth2", - "description": "Google API access", "config": { - "oauth2": { - "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", - "tokenUrl": "https://oauth2.googleapis.com/token", - "clientId": "your-client-id", - "clientSecret": "your-client-secret", - "defaultScopes": ["openid", "email", "https://www.googleapis.com/auth/calendar"] - } + "clientId": "your-client-id", + "clientSecret": "your-client-secret", + "scopes": ["openid", "email", "https://www.googleapis.com/auth/calendar"] } }' ``` -### Supported Auth Types - -| Type | Use Case | Config Section | -|------|----------|---------------| -| `jwt` | Service tokens, API JWTs | `config.jwt` | -| `api_key` | API keys (Stripe, OpenAI, etc.) | `config.apiKey` | -| `oauth2` | OAuth2 providers (Google, Slack, etc.) | `config.oauth2` | -| `basic` | Username/password credentials | `config.basic` | - -## Agent Management - -### Register an Agent +### `jwt` provider ```bash -curl -X POST http://localhost:3001/api/v1/admin/agents \ +curl -X POST https:///api/v1/credential-providers \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ - "name": "Customer Support Agent", - "upstreamUrl": "http://support-agent:8000", - "description": "Handles customer inquiries", - "requiredCredentials": [ - { "serviceType": "zendesk", "description": "Zendesk API access" } - ], - "tenantId": "tenant-acme" + "name": "Internal API", + "serviceType": "internal_api", + "authType": "jwt" }' ``` -### Kill Switch - -Instantly disable an agent without deleting it: - -```bash -# Disable -- all invocations return 403 -curl -X POST http://localhost:3001/api/v1/admin/agents//disable \ - -H "Authorization: Bearer " - -# Re-enable -curl -X POST http://localhost:3001/api/v1/admin/agents//enable \ - -H "Authorization: Bearer " -``` - -### Regenerate Runtime Token +### Supported auth types -If an agent's runtime token is compromised: +| Type | Use case | +|------|----------| +| `api_key` | API keys (Stripe, OpenAI, etc.) | +| `oauth2` | OAuth2 providers (Google, GitHub, Slack, etc.) | +| `jwt` | Service tokens and JWTs | +| `basic` | Username/password credentials | -```bash -curl -X POST http://localhost:3001/api/v1/admin/agents//regenerate-token \ - -H "Authorization: Bearer " -# Returns new runtime_token (old one is immediately invalidated) -``` +--- ## Multi-Tenancy -The Gateway supports tenant isolation across all resources: - -- **Agents** are scoped to a `tenantId` -- **Users** belong to a tenant -- **Credentials** are isolated per user and tenant -- **Tool Providers** can be global or tenant-scoped -- **ACL rules** are tenant-aware - -Users in different tenants cannot see or access each other's agents and credentials. +All resources can be scoped to a `tenantId`. Users and agents in different tenants are fully isolated — they cannot see or access each other's agents, credentials, or providers. -### Setting Up Tenants +- `admin` — can manage all tenants; pass `tenantId` in request body to scope operations +- `tenant_admin` and `agent_creator` — automatically pinned to their own tenant -1. Create users with tenant IDs: +Resources with `tenantId: null` are global and visible to all tenants. -```bash -curl -X POST http://localhost:3001/api/v1/admin/users \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{ - "email": "dev@acme.com", - "password": "password123", - "tenantId": "tenant-acme", - "roles": ["agent_creator"] - }' -``` - -2. Agents created by tenant users inherit the tenant: - -```bash -# When dev@acme.com creates an agent, it gets tenantId: "tenant-acme" -# Users from other tenants cannot see or invoke it -``` - -## Monitoring and Audit +--- -### Audit Logs +## Audit Logs -Query all tool calls with full context: +Every tool call is recorded with full context. ```bash -# Get recent logs -curl "http://localhost:3001/api/v1/audit/logs?limit=50" \ - -H "Authorization: Bearer " - -# Filter by agent -curl "http://localhost:3001/api/v1/audit/logs?agent_id=abc123" \ +# Recent logs +curl "https:///api/v1/audit/logs?limit=50" \ -H "Authorization: Bearer " -# Filter by status -curl "http://localhost:3001/api/v1/audit/logs?status=failed" \ +# Filter by agent, user, tool, or status +curl "https:///api/v1/audit/logs?agent_id=abc&status=failed" \ -H "Authorization: Bearer " -# Filter by tool name -curl "http://localhost:3001/api/v1/audit/logs?tool_name=transfer_money" \ +# Aggregate stats +curl https:///api/v1/audit/stats \ -H "Authorization: Bearer " ``` -Each audit log entry contains: +**Query parameters:** `user_id`, `agent_id`, `tool_name`, `status`, `start_date`, `end_date`, `limit`, `offset` + +**Log entry fields:** | Field | Description | |-------|-------------| -| `userId` | Who authenticated the request | -| `agentId` | Which agent made the call | -| `endUserId` | The human end-user | -| `providerId` | Which tool provider handled it | -| `toolName` | The tool that was called | +| `userId` | Authenticated user | +| `agentId` | Agent that made the call | +| `endUserId` | End user the agent was acting on behalf of | +| `providerId` | Tool provider that handled the call | +| `toolName` | Tool invoked | | `arguments` | Tool call arguments (JSON) | -| `result` | Tool call result (JSON) | -| `status` | `pending`, `completed`, `failed`, `confirmed`, `rejected` | -| `duration` | Execution time in milliseconds | - -### Audit Statistics - -```bash -curl http://localhost:3001/api/v1/audit/stats \ - -H "Authorization: Bearer " -``` +| `result` | Tool response (JSON) | +| `status` | `pending` / `confirmed` / `rejected` / `completed` / `failed` | +| `duration` | Execution time in ms | +| `confirmedBy` | User who approved the confirmation (if applicable) | -### Health Check - -```bash -curl http://localhost:3001/api/health -# Response: -# { -# "status": "healthy", -# "services": { "gateway": "healthy", "mcp": "healthy" }, -# "metrics": { "pendingConfirmations": 0, "mcpLatency": 45 } -# } -``` - -## Dashboard (gateway-app) - -The Gateway includes an optional Next.js dashboard with an AI assistant powered by CopilotKit + Strands/AG-UI. - -### Starting the Dashboard - -```bash -cd gateway-app -pnpm install -pnpm dev -# Open http://localhost:3000 -``` - -### Starting the AI Agent - -```bash -cd gateway-app/agent -pip install -r requirements.txt # or: uv sync -uv run main.py -# Agent runs on http://localhost:8000 -``` - -The dashboard provides: -- **Agent management** -- create, update, delete, enable/disable agents -- **Tool provider management** -- configure MCP server routing -- **Confirmation management** -- review and respond to pending confirmations -- **Audit log viewer** -- search and filter tool call history -- **AI chat assistant** -- manage the Gateway via natural language - -## Deployment - -### Vercel - -The Gateway is built with Hono and configured for Vercel deployment: - -```bash -# Build and deploy -pnpm build -pnpm deploy - -# Or use Vercel CLI -vc deploy -``` - -Set environment variables in the Vercel dashboard. Use PostgreSQL for production (`DATABASE_URL=postgres://...`). - -### Docker (Custom) - -Create a `Dockerfile`: +--- -```dockerfile -FROM node:20-slim -WORKDIR /app -COPY package.json pnpm-lock.yaml ./ -RUN npm install -g pnpm && pnpm install --frozen-lockfile -COPY . . -EXPOSE 3001 -CMD ["pnpm", "dev"] -``` +## Production Checklist -### Production Checklist +- [ ] Set a strong `JWT_SECRET` (`openssl rand -hex 32`) +- [ ] Generate a `CREDENTIAL_ENCRYPTION_KEY` (`openssl rand -hex 32`) +- [ ] Use PostgreSQL (`DATABASE_URL=postgres://...`) +- [ ] Change the default admin password immediately after first login +- [ ] Set `GATEWAY_PUBLIC_URL` for OAuth callbacks and pairing links +- [ ] Configure `JWT_EXTERNAL_ISSUERS` if using an external IdP +- [ ] Enable HTTPS via a reverse proxy (nginx, Caddy) or cloud platform +- [ ] Set up monitoring for the `/health` endpoint -- [ ] Set a strong `JWT_SECRET` (or use asymmetric keys with `JWT_PUBLIC_KEY`) -- [ ] Generate a random `CREDENTIAL_ENCRYPTION_KEY` (64-char hex) -- [ ] Use PostgreSQL for production (`DATABASE_URL=postgres://...`) -- [ ] Change the default admin password -- [ ] Configure CORS origins (update `cors()` in `src/index.ts`) -- [ ] Set up external IdP for user authentication (`JWT_EXTERNAL_ISSUERS`) -- [ ] Configure audit log retention policy -- [ ] Set up monitoring for the `/api/health` endpoint -- [ ] Enable HTTPS (via reverse proxy or platform) +--- ## Admin API Reference @@ -708,7 +513,7 @@ CMD ["pnpm", "dev"] | `/api/v1/admin/agents/:id` | GET | Get agent | | `/api/v1/admin/agents/:id` | PUT | Update agent | | `/api/v1/admin/agents/:id` | DELETE | Delete agent | -| `/api/v1/admin/agents/:id/disable` | POST | Kill switch | +| `/api/v1/admin/agents/:id/disable` | POST | Kill switch — disable | | `/api/v1/admin/agents/:id/enable` | POST | Re-enable | | `/api/v1/admin/agents/:id/regenerate-token` | POST | New runtime token | | **API Keys** | | | @@ -721,20 +526,26 @@ CMD ["pnpm", "dev"] | `/api/v1/admin/tool-providers/:id` | GET | Get provider | | `/api/v1/admin/tool-providers/:id` | PUT | Update provider | | `/api/v1/admin/tool-providers/:id` | DELETE | Delete provider | +| `/api/v1/admin/tool-providers/:id/tools` | GET | Fetch tools from MCP server | | **Provider Access** | | | -| `/api/v1/admin/provider-access` | GET | List ACL rules | +| `/api/v1/admin/provider-access` | GET | List rules | | `/api/v1/admin/provider-access` | POST | Create rule | | `/api/v1/admin/provider-access/:id` | GET | Get rule | +| `/api/v1/admin/provider-access/:id` | PUT | Update rule | | `/api/v1/admin/provider-access/:id` | DELETE | Delete rule | +| `/api/v1/admin/provider-access/agent/:id` | GET | Get agent's rules | +| `/api/v1/admin/provider-access/agent/:id` | PUT | Set agent's rules (atomic) | +| `/api/v1/admin/provider-access/evaluate` | POST | Evaluate policy | | **Credential Providers** | | | | `/api/v1/credential-providers` | GET | List providers | | `/api/v1/credential-providers` | POST | Create provider | | `/api/v1/credential-providers/:id` | GET | Get provider | | `/api/v1/credential-providers/:id` | PUT | Update provider | | `/api/v1/credential-providers/:id` | DELETE | Delete provider | +| `/api/v1/credential-providers/by-service/:type` | GET | Get by service type | | **Audit** | | | | `/api/v1/audit/logs` | GET | Query logs | -| `/api/v1/audit/logs/:id` | GET | Get log | -| `/api/v1/audit/stats` | GET | Statistics | +| `/api/v1/audit/logs/:id` | GET | Get log entry | +| `/api/v1/audit/stats` | GET | Aggregate statistics | | **Health** | | | -| `/api/health` | GET | Health check | +| `/health` | GET | Health check | diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx deleted file mode 100644 index b3f8077..0000000 --- a/docs/content/docs/index.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Introduction -description: Enterprise-grade Agent Gateway providing identity, security, credential management, and policy enforcement for AI agents. ---- - -Simplaix Gateway is an enterprise-grade Agent Gateway that provides identity, security, credential management, and policy enforcement for AI agents. It supports multiple agent protocols including [MCP](https://modelcontextprotocol.io/), CopilotKit, AG-UI/Strands, and any HTTP-based agent runtime. - -## Key Features - -- **Multi-Protocol Agent Routing** -- Route requests to any HTTP-based agent runtime (MCP servers, CopilotKit agents, Strands/AG-UI agents, custom runtimes) -- **Virtual Agent Identity** -- Register agents with upstream URLs, kill switch, and tenant isolation -- **Dual Authentication** -- JWT for admins and end-users, API Keys (`gk_`) for server-to-server -- **Credential Vault** -- Encrypted per-user credential storage with automatic resolution and injection -- **Policy Engine** -- Configurable rules: allow, deny, or require human confirmation per tool -- **Human-in-the-Loop Confirmation** -- SSE-based real-time confirmation workflow for sensitive operations -- **Comprehensive Audit Trail** -- Track every tool call with full context and timing -- **Multi-Tenancy** -- Tenant isolation across agents, credentials, and users -- **Credential SDKs** -- Python and TypeScript SDKs for agents to resolve user credentials - -## Supported Agent Protocols - -| Protocol | Integration Point | Description | -|----------|------------------|-------------| -| **MCP** | `/api/v1/mcp/*` | JSON-RPC tool calls to MCP servers with policy enforcement | -| **HTTP Agent** | `/api/v1/agents/:id/invoke` | Any HTTP-based agent runtime (JSON or SSE streaming) | -| **CopilotKit** | Via agent invoke | CopilotKit agents with AG-UI SSE streaming | -| **Strands / AG-UI** | Via agent invoke | Strands framework agents with AG-UI protocol | - -## Guides by Role - - - - - - - -## Quick Links - - - - - - - diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json index d57ebd7..bd60313 100644 --- a/docs/content/docs/meta.json +++ b/docs/content/docs/meta.json @@ -1,10 +1,18 @@ { "title": "Documentation", "pages": [ - "index", - "getting-started", - "guides", + "---Get started---", + "getting-started/index", + "getting-started/quick-start", + "---Guides---", + "guides/agent-creator", + "guides/app-builder", + "guides/gateway-admin", "concepts", + "---Deployment---", + "getting-started/deployment", + + "---Reference---", "authentication", "api-reference", "sdks" diff --git a/docs/content/docs/sdks/python.mdx b/docs/content/docs/sdks/python.mdx index cbca93d..8f34fd2 100644 --- a/docs/content/docs/sdks/python.mdx +++ b/docs/content/docs/sdks/python.mdx @@ -17,7 +17,7 @@ pip install simplaix-credential-sdk from simplaix_credential_sdk import create_credential_client client = create_credential_client( - gateway_url="http://localhost:3001/api", + gateway_url="https:///api", api_key="gk_xxx" ) @@ -40,7 +40,7 @@ The SDK provides Starlette middleware for automatic context setup: from simplaix_credential_sdk import create_credential_client client = create_credential_client( - gateway_url="http://localhost:3001/api", + gateway_url="https:///api", api_key="gk_xxx" ) @@ -56,7 +56,7 @@ The middleware automatically extracts `X-Credential-*` headers and makes them av from simplaix_credential_sdk import create_credential_client client = create_credential_client( - gateway_url="http://localhost:3001/api", + gateway_url="https:///api", api_key="gk_xxx" ) diff --git a/docs/package.json b/docs/package.json index 4bd714e..18e28d3 100755 --- a/docs/package.json +++ b/docs/package.json @@ -14,9 +14,12 @@ "fumadocs-mdx": "14.2.6", "fumadocs-ui": "16.5.1", "lucide-react": "^0.563.0", + "mermaid": "^11.12.3", "next": "16.1.6", + "next-themes": "^0.4.6", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-icons": "^5.6.0", "tailwind-merge": "^3.4.0" }, "devDependencies": { diff --git a/docs/source.config.ts b/docs/source.config.ts index 63cedfa..fdbd485 100755 --- a/docs/source.config.ts +++ b/docs/source.config.ts @@ -1,4 +1,5 @@ import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from 'fumadocs-mdx/config'; +import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins'; // You can customise Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.dev/docs/mdx/collections @@ -17,6 +18,6 @@ export const docs = defineDocs({ export default defineConfig({ mdxOptions: { - // MDX options + remarkPlugins: [remarkMdxMermaid], }, }); diff --git a/docs/src/app/(home)/page.tsx b/docs/src/app/(home)/page.tsx index 4450909..4393163 100644 --- a/docs/src/app/(home)/page.tsx +++ b/docs/src/app/(home)/page.tsx @@ -1,53 +1,5 @@ -import Link from 'next/link'; +import { redirect } from 'next/navigation'; export default function HomePage() { - return ( -
-
-

Simplaix Gateway

-

- Enterprise-grade Agent Gateway providing identity, security, credential - management, and policy enforcement for AI agents. Supports MCP, - CopilotKit, AG-UI/Strands, and any HTTP-based agent runtime. -

-
- - Get Started - - - API Reference - -
-
-
-

Multi-Protocol Routing

-

- Route to MCP servers, CopilotKit, Strands/AG-UI, or any HTTP agent - with identity and credential injection. -

-
-
-

Credential Vault

-

- Encrypted per-user credential storage with automatic resolution - and injection. -

-
-
-

Policy Engine

-

- Configurable rules: allow, deny, or require human confirmation per - tool. -

-
-
-
-
- ); + redirect('/docs'); } diff --git a/docs/src/app/docs/docs-layout-client.tsx b/docs/src/app/docs/docs-layout-client.tsx new file mode 100644 index 0000000..8dbabb6 --- /dev/null +++ b/docs/src/app/docs/docs-layout-client.tsx @@ -0,0 +1,32 @@ +'use client'; + +import * as PageTree from 'fumadocs-core/page-tree'; +import { DocsLayout } from 'fumadocs-ui/layouts/docs'; +import { baseOptions } from '@/lib/layout.shared'; +import { ReactNode } from 'react'; + +function SidebarSeparator({ item }: { item: PageTree.Separator }) { + return ( +

+ {item.name} +

+ ); +} + +export function DocsLayoutClient({ + tree, + children, +}: { + tree: PageTree.Root; + children: ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/docs/src/app/docs/layout.tsx b/docs/src/app/docs/layout.tsx index a373143..673eb67 100644 --- a/docs/src/app/docs/layout.tsx +++ b/docs/src/app/docs/layout.tsx @@ -1,11 +1,10 @@ import { source } from '@/lib/source'; -import { DocsLayout } from 'fumadocs-ui/layouts/docs'; -import { baseOptions } from '@/lib/layout.shared'; +import { DocsLayoutClient } from './docs-layout-client'; export default function Layout({ children }: LayoutProps<'/docs'>) { return ( - + {children} - + ); } diff --git a/docs/src/app/global.css b/docs/src/app/global.css index 50b3bc2..6dfc53d 100644 --- a/docs/src/app/global.css +++ b/docs/src/app/global.css @@ -1,3 +1,12 @@ @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; + +/* Mermaid zoom modal */ +[data-rmiz-modal-overlay='visible'] { + background-color: hsl(var(--background) / 0.9); + backdrop-filter: blur(4px); +} +[data-rmiz-modal-img] { + border-radius: 0.5rem; +} diff --git a/docs/src/components/mermaid.tsx b/docs/src/components/mermaid.tsx new file mode 100644 index 0000000..5ee8d36 --- /dev/null +++ b/docs/src/components/mermaid.tsx @@ -0,0 +1,77 @@ +'use client'; + +import { use, useEffect, useId, useRef, useState } from 'react'; +import { useTheme } from 'next-themes'; +import Zoom from 'react-medium-image-zoom'; +import 'react-medium-image-zoom/dist/styles.css'; + +export function Mermaid({ chart }: { chart: string }) { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) return null; + return ; +} + +const cache = new Map>(); + +function cachePromise(key: string, setPromise: () => Promise): Promise { + const cached = cache.get(key); + if (cached) return cached as Promise; + const promise = setPromise(); + cache.set(key, promise); + return promise; +} + +function MermaidContent({ chart }: { chart: string }) { + const id = useId(); + const bindRef = useRef(null); + const { resolvedTheme } = useTheme(); + const { default: mermaid } = use(cachePromise('mermaid', () => import('mermaid'))); + + mermaid.initialize({ + startOnLoad: false, + securityLevel: 'loose', + fontFamily: 'inherit', + theme: resolvedTheme === 'dark' ? 'dark' : 'neutral', + themeVariables: + resolvedTheme === 'dark' + ? {} + : { + background: 'transparent', + primaryColor: '#e2e8f0', + primaryTextColor: '#0f172a', + primaryBorderColor: '#cbd5e1', + lineColor: '#64748b', + secondaryColor: '#f1f5f9', + tertiaryColor: '#f8fafc', + edgeLabelBackground: '#f8fafc', + clusterBkg: '#f1f5f9', + clusterBorder: '#cbd5e1', + titleColor: '#0f172a', + nodeTextColor: '#0f172a', + }, + }); + + const { svg, bindFunctions } = use( + cachePromise(`${chart}-${resolvedTheme}`, () => + mermaid.render(id, chart.replaceAll('\\n', '\n')), + ), + ); + + return ( + +
{ + (bindRef as React.MutableRefObject).current = el; + if (el) bindFunctions?.(el); + }} + className="overflow-x-auto rounded-lg border border-fd-border bg-fd-card px-6 py-4 my-4 flex justify-center cursor-zoom-in" + dangerouslySetInnerHTML={{ __html: svg }} + /> + + ); +} diff --git a/docs/src/lib/layout.shared.tsx b/docs/src/lib/layout.shared.tsx index b578975..e5377c0 100644 --- a/docs/src/lib/layout.shared.tsx +++ b/docs/src/lib/layout.shared.tsx @@ -1,4 +1,5 @@ import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; +import { FaGithub } from 'react-icons/fa'; export function baseOptions(): BaseLayoutProps { return { @@ -7,13 +8,21 @@ export function baseOptions(): BaseLayoutProps { }, links: [ { - text: 'Docs', - url: '/docs', - active: 'nested-url', + type: 'icon', + text: 'GitHub', + label: 'GitHub', + url: 'https://github.com/simplaix/simplaix-gateway', + icon: , + external: true, + on: 'nav', }, { - text: 'GitHub', + type: 'main', + text: 'simplaix/simplaix-gateway', url: 'https://github.com/simplaix/simplaix-gateway', + icon: , + external: true, + on: 'menu', }, ], }; diff --git a/docs/src/mdx-components.tsx b/docs/src/mdx-components.tsx index ea62bd7..411414d 100644 --- a/docs/src/mdx-components.tsx +++ b/docs/src/mdx-components.tsx @@ -1,5 +1,6 @@ import defaultMdxComponents from 'fumadocs-ui/mdx'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; +import { Mermaid } from '@/components/mermaid'; import type { MDXComponents } from 'mdx/types'; export function getMDXComponents(components?: MDXComponents): MDXComponents { @@ -7,6 +8,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents { ...defaultMdxComponents, Tab, Tabs, + Mermaid, ...components, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30dca12..8a19c06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,15 +84,24 @@ importers: lucide-react: specifier: ^0.563.0 version: 0.563.0(react@19.2.4) + mermaid: + specifier: ^11.12.3 + version: 11.12.3 next: specifier: 16.1.6 version: 16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: ^19.2.4 version: 19.2.4 react-dom: specifier: ^19.2.4 version: 19.2.4(react@19.2.4) + react-icons: + specifier: ^5.6.0 + version: 5.6.0(react@19.2.4) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -432,18 +441,33 @@ packages: '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + '@chevrotain/cst-dts-gen@11.1.2': + resolution: {integrity: sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==} + '@chevrotain/gast@11.0.3': resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + '@chevrotain/gast@11.1.2': + resolution: {integrity: sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==} + '@chevrotain/regexp-to-ast@11.0.3': resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + '@chevrotain/regexp-to-ast@11.1.2': + resolution: {integrity: sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==} + '@chevrotain/types@11.0.3': resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@chevrotain/utils@11.1.2': + resolution: {integrity: sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==} + '@copilotkit/react-core@1.50.0': resolution: {integrity: sha512-JfUEvmgXgPz7wIQq9EFXWGDMtYLIVKSNqPdJROEomZXLhREDlxpg+jr5KHvoOPUlSnVLzuPObRKSdeJTwVOGsQ==} peerDependencies: @@ -1382,6 +1406,9 @@ packages: '@mermaid-js/parser@0.6.3': resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@mermaid-js/parser@1.0.0': + resolution: {integrity: sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==} + '@modelcontextprotocol/sdk@1.26.0': resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} engines: {node: '>=18'} @@ -3068,6 +3095,9 @@ packages: chevrotain@11.0.3: resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + chevrotain@11.1.2: + resolution: {integrity: sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -4637,6 +4667,10 @@ packages: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} + langium@4.2.1: + resolution: {integrity: sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==} + engines: {node: '>=20.10.0', npm: '>=10.2.3'} + langsmith@0.3.87: resolution: {integrity: sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q==} peerDependencies: @@ -4916,6 +4950,9 @@ packages: mermaid@11.12.2: resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + mermaid@11.12.3: + resolution: {integrity: sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -5603,6 +5640,11 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-icons@5.6.0: + resolution: {integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==} + peerDependencies: + react: '*' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -6464,6 +6506,9 @@ packages: vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -6818,17 +6863,34 @@ snapshots: '@chevrotain/types': 11.0.3 lodash-es: 4.17.21 + '@chevrotain/cst-dts-gen@11.1.2': + dependencies: + '@chevrotain/gast': 11.1.2 + '@chevrotain/types': 11.1.2 + lodash-es: 4.17.23 + '@chevrotain/gast@11.0.3': dependencies: '@chevrotain/types': 11.0.3 lodash-es: 4.17.21 + '@chevrotain/gast@11.1.2': + dependencies: + '@chevrotain/types': 11.1.2 + lodash-es: 4.17.23 + '@chevrotain/regexp-to-ast@11.0.3': {} + '@chevrotain/regexp-to-ast@11.1.2': {} + '@chevrotain/types@11.0.3': {} + '@chevrotain/types@11.1.2': {} + '@chevrotain/utils@11.0.3': {} + '@chevrotain/utils@11.1.2': {} + '@copilotkit/react-core@1.50.0(@types/mdast@4.0.4)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql@16.12.0)(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@3.25.76)': dependencies: '@ag-ui/client': 0.0.42 @@ -7689,6 +7751,10 @@ snapshots: dependencies: langium: 3.3.1 + '@mermaid-js/parser@1.0.0': + dependencies: + langium: 4.2.1 + '@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.7) @@ -9476,6 +9542,11 @@ snapshots: chevrotain: 11.0.3 lodash-es: 4.17.23 + chevrotain-allstar@0.3.1(chevrotain@11.1.2): + dependencies: + chevrotain: 11.1.2 + lodash-es: 4.17.23 + chevrotain@11.0.3: dependencies: '@chevrotain/cst-dts-gen': 11.0.3 @@ -9485,6 +9556,15 @@ snapshots: '@chevrotain/utils': 11.0.3 lodash-es: 4.17.21 + chevrotain@11.1.2: + dependencies: + '@chevrotain/cst-dts-gen': 11.1.2 + '@chevrotain/gast': 11.1.2 + '@chevrotain/regexp-to-ast': 11.1.2 + '@chevrotain/types': 11.1.2 + '@chevrotain/utils': 11.1.2 + lodash-es: 4.17.23 + chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -11319,6 +11399,14 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 + langium@4.2.1: + dependencies: + chevrotain: 11.1.2 + chevrotain-allstar: 0.3.1(chevrotain@11.1.2) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + langsmith@0.3.87(@opentelemetry/api@1.9.0)(openai@4.104.0(ws@8.19.0)(zod@3.25.76)): dependencies: '@types/uuid': 10.0.0 @@ -11718,6 +11806,29 @@ snapshots: ts-dedent: 2.2.0 uuid: 11.1.0 + mermaid@11.12.3: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.0 + '@mermaid-js/parser': 1.0.0 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.19 + dompurify: 3.3.1 + katex: 0.16.28 + khroma: 2.1.0 + lodash-es: 4.17.23 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + methods@1.1.2: {} micromark-core-commonmark@1.1.0: @@ -12706,6 +12817,10 @@ snapshots: dependencies: react: 19.2.4 + react-icons@5.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + react-is@16.13.1: {} react-is@18.3.1: {} @@ -13902,6 +14017,8 @@ snapshots: vscode-uri@3.0.8: {} + vscode-uri@3.1.0: {} + web-namespaces@2.0.1: {} web-streams-polyfill@4.0.0-beta.3: {} From 285cccceea41a958db9d30450fb043065e23b456 Mon Sep 17 00:00:00 2001 From: penghanyuan Date: Sat, 7 Mar 2026 22:58:30 +0100 Subject: [PATCH 2/2] Update pnpm-lock.yaml with new dependencies, enhance documentation with new API reference sections for notifications and tool providers, and restructure existing API documentation for clarity. Add new endpoints for device notifications and tool gate policy evaluation. --- docs/content/docs/api-reference/agents.mdx | 121 +++++------- docs/content/docs/api-reference/api-keys.mdx | 67 ++++--- docs/content/docs/api-reference/audit.mdx | 73 ++++---- docs/content/docs/api-reference/auth.mdx | 102 +++++++---- .../docs/api-reference/confirmations.mdx | 66 +++---- .../api-reference/credential-providers.mdx | 63 ++----- .../docs/api-reference/credentials.mdx | 80 ++++---- docs/content/docs/api-reference/mcp-proxy.mdx | 135 +++++++------- docs/content/docs/api-reference/meta.json | 10 +- .../docs/api-reference/notifications.mdx | 42 +++++ .../docs/api-reference/provider-access.mdx | 62 +++++++ docs/content/docs/api-reference/streaming.mdx | 59 +++--- docs/content/docs/api-reference/tool-gate.mdx | 54 ++++++ .../docs/api-reference/tool-providers.mdx | 44 +++++ docs/content/docs/authentication/api-keys.mdx | 72 -------- docs/content/docs/authentication/index.mdx | 38 ++-- docs/content/docs/authentication/jwt.mdx | 75 -------- docs/content/docs/authentication/meta.json | 2 +- .../content/docs/getting-started/docs-mcp.mdx | 105 +++++++++++ docs/content/docs/meta.json | 1 + docs/package.json | 5 +- docs/public/claude-code-skill.md | 17 ++ docs/public/logo.png | Bin 0 -> 27509 bytes docs/public/mcp.json | 8 + docs/src/app/api/[transport]/route.ts | 120 ++++++++++++ docs/src/app/favicon.ico | Bin 0 -> 15406 bytes docs/src/lib/layout.shared.tsx | 8 +- pnpm-lock.yaml | 172 ++++++++++++++++++ 28 files changed, 1030 insertions(+), 571 deletions(-) create mode 100644 docs/content/docs/api-reference/notifications.mdx create mode 100644 docs/content/docs/api-reference/provider-access.mdx create mode 100644 docs/content/docs/api-reference/tool-gate.mdx create mode 100644 docs/content/docs/api-reference/tool-providers.mdx delete mode 100644 docs/content/docs/authentication/api-keys.mdx delete mode 100644 docs/content/docs/authentication/jwt.mdx create mode 100644 docs/content/docs/getting-started/docs-mcp.mdx create mode 100644 docs/public/claude-code-skill.md create mode 100644 docs/public/logo.png create mode 100644 docs/public/mcp.json create mode 100644 docs/src/app/api/[transport]/route.ts create mode 100644 docs/src/app/favicon.ico diff --git a/docs/content/docs/api-reference/agents.mdx b/docs/content/docs/api-reference/agents.mdx index c6aa09b..45cd7a3 100644 --- a/docs/content/docs/api-reference/agents.mdx +++ b/docs/content/docs/api-reference/agents.mdx @@ -1,11 +1,32 @@ --- title: Agents -description: CRUD operations for managing agents, including invocation and credential pre-checks. +description: Admin agent management, runtime token lifecycle, and agent invoke APIs. --- -All agent management endpoints require JWT authentication. +## Endpoint Map -## Register Agent +### Admin Management (`/api/v1/admin/agents`) + +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `POST` | `/api/v1/admin/agents` | JWT + `agent:create` | Create agent and return one-time runtime token | +| `GET` | `/api/v1/admin/agents` | JWT + `agent:read` | List agents | +| `GET` | `/api/v1/admin/agents/:id` | JWT + `agent:read` | Get agent details | +| `PUT` | `/api/v1/admin/agents/:id` | JWT + `agent:update:own` or `agent:update:all` | Update agent | +| `DELETE` | `/api/v1/admin/agents/:id` | JWT + delete permission | Delete agent | +| `POST` | `/api/v1/admin/agents/:id/disable` | JWT + update permission | Kill-switch disable | +| `POST` | `/api/v1/admin/agents/:id/enable` | JWT + update permission | Re-enable | +| `POST` | `/api/v1/admin/agents/:id/regenerate-token` | JWT + update permission | Rotate runtime token | + +### Runtime/User Routes (`/api/v1/agents`) + +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `POST` | `/api/v1/agents/:agentId/invoke` | Flexible auth | Invoke upstream agent runtime | +| `GET` | `/api/v1/agents/:agentId/credentials-check` | Flexible auth | Pre-check required credentials | +| `GET` | `/api/v1/agents/:agentId` | Flexible auth | Get public agent info | + +## Create Agent ```bash POST /api/v1/admin/agents @@ -20,73 +41,28 @@ Content-Type: application/json } ``` -**Response** `201 Created` - ```json { - "id": "agt_...", - "name": "Finance Bot", - "upstreamUrl": "https://finance-agent.internal/mcp", - "isActive": true, - "requiredCredentials": [{ "serviceType": "gateway_api" }], - "description": "Handles financial queries", - "tenantId": "tnt_...", - "createdAt": "2025-01-01T00:00:00.000Z" + "success": true, + "agent": { + "id": "agt_...", + "name": "Finance Bot", + "upstreamUrl": "https://finance-agent.internal/mcp", + "hasUpstreamSecret": false, + "isActive": true, + "requireConfirmation": false, + "requiredCredentials": [{ "serviceType": "gateway_api" }], + "runtimeTokenPrefix": "art_xxxx", + "createdAt": "2026-03-07T12:00:00.000Z" + }, + "runtime_token": "art_..." } ``` -## List Agents - -```bash -GET /api/v1/admin/agents -Authorization: Bearer -``` - -## Get Agent - -```bash -GET /api/v1/admin/agents/:id -Authorization: Bearer -``` - -## Update Agent - -```bash -PUT /api/v1/admin/agents/:id -Authorization: Bearer -Content-Type: application/json - -{ - "name": "Updated Finance Bot", - "upstreamUrl": "https://new-finance-agent.internal/mcp" -} -``` - -## Delete Agent - -```bash -DELETE /api/v1/admin/agents/:id -Authorization: Bearer -``` - -## Disable Agent (Kill Switch) - -```bash -POST /api/v1/admin/agents/:id/disable -Authorization: Bearer -``` - -## Enable Agent - -```bash -POST /api/v1/admin/agents/:id/enable -Authorization: Bearer -``` +`runtime_token` plaintext is only returned on create/rotate. ## Invoke Agent -Invoke an agent from the frontend. This endpoint is protocol-agnostic -- it forwards the request to the agent's `upstreamUrl` and supports both JSON and SSE streaming responses. The upstream can be any HTTP-based agent runtime (MCP server, CopilotKit agent, Strands/AG-UI agent, or custom service). The Gateway performs credential pre-checks and injects user identity and resolved credentials as headers before forwarding. - ```bash POST /api/v1/agents/:agentId/invoke Authorization: Bearer @@ -97,25 +73,26 @@ Content-Type: application/json } ``` -If required credentials are missing, the response includes auth URLs: +If credentials are missing: ```json { - "status": "CREDENTIALS_REQUIRED", - "missingCredentials": [ - { - "serviceType": "gateway_api", - "connectUrl": "/auth/connect?service=gateway_api" - } - ] + "code": "CREDENTIALS_REQUIRED", + "missing": ["gateway_api"], + "authUrls": { + "gateway_api": "/auth/connect?service=gateway_api" + }, + "message": "Authentication required for: gateway_api" } ``` -## Pre-check Credentials +On success, response is JSON-wrapped (`{ success, request_id, data }`) or forwarded SSE stream. -Check credential availability without invoking the agent. +## Credentials Pre-check ```bash GET /api/v1/agents/:agentId/credentials-check Authorization: Bearer ``` + +Returns `{ ok: true }` when all required credentials are present; otherwise `401` with `CREDENTIALS_REQUIRED` payload. diff --git a/docs/content/docs/api-reference/api-keys.mdx b/docs/content/docs/api-reference/api-keys.mdx index 5321a81..4612de7 100644 --- a/docs/content/docs/api-reference/api-keys.mdx +++ b/docs/content/docs/api-reference/api-keys.mdx @@ -1,11 +1,25 @@ --- title: API Keys -description: Create, list, and revoke Gateway API keys (gk_) for server-to-server authentication. +description: Admin APIs for creating, listing, and revoking Gateway API keys (`gk_...`). --- -All API key management endpoints require JWT authentication (admin). +## Endpoint Map -## Create API Key +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `POST` | `/api/v1/admin/api-keys` | JWT + role `admin`/`tenant_admin` | Create API key | +| `GET` | `/api/v1/admin/api-keys` | JWT + role `admin`/`tenant_admin` | List key metadata | +| `DELETE` | `/api/v1/admin/api-keys/:id` | JWT + role `admin`/`tenant_admin` | Revoke key | + +## Scopes + +| Scope | Description | +|---|---| +| `credentials:resolve` | Resolve/check credentials | +| `credentials:read` | Read credential metadata | +| `credentials:write` | Store/update credentials | + +## Create Key ```bash POST /api/v1/admin/api-keys @@ -13,49 +27,34 @@ Authorization: Bearer Content-Type: application/json { - "name": "Agent Server", - "scopes": ["credentials:resolve"] + "name": "Agent Runtime", + "scopes": ["credentials:resolve"], + "expiresAt": "2026-06-01T00:00:00.000Z" } ``` -**Response** `201 Created` - ```json { - "id": "key_...", - "key": "gk_xxx...", - "name": "Agent Server", - "scopes": ["credentials:resolve"], - "keyPrefix": "gk_xxxx", - "isActive": true, - "createdAt": "2025-01-01T00:00:00.000Z" + "success": true, + "message": "API key created. Store this key securely — it will not be shown again!", + "key": "gk_...", + "keyRecord": { + "id": "...", + "keyPrefix": "gk_xxxx", + "name": "Agent Runtime", + "scopes": ["credentials:resolve"], + "isActive": true + } } ``` -The full `key` value is only returned at creation time. Store it securely. - -## Available Scopes +The full `key` is returned only once. -| Scope | Description | -|-------|-------------| -| `credentials:resolve` | Resolve credentials for a user | -| `credentials:read` | Read credential metadata | -| `credentials:write` | Store or update credentials | - -## List API Keys +## List Keys ```bash GET /api/v1/admin/api-keys Authorization: Bearer ``` -Returns key metadata including prefix, name, scopes, and status. The full key is never returned. - -## Revoke API Key - -```bash -DELETE /api/v1/admin/api-keys/:id -Authorization: Bearer -``` - -Revoking an API key immediately invalidates it. Any agent runtimes using it will receive authentication errors. +Returns metadata only (never raw key). diff --git a/docs/content/docs/api-reference/audit.mdx b/docs/content/docs/api-reference/audit.mdx index 9374b1b..1747cf9 100644 --- a/docs/content/docs/api-reference/audit.mdx +++ b/docs/content/docs/api-reference/audit.mdx @@ -1,60 +1,59 @@ --- -title: Audit Logs -description: Query and retrieve audit logs for tool calls and operations. +title: Audit +description: Query audit logs and aggregate audit statistics. --- -The audit service tracks every tool call with full context and timing. All audit endpoints require JWT authentication. +## Endpoint Map + +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `GET` | `/api/v1/audit/logs` | JWT | Query audit logs | +| `GET` | `/api/v1/audit/logs/:id` | JWT | Get one audit log | +| `GET` | `/api/v1/audit/stats` | JWT + `admin` role | Get aggregate stats | ## Query Logs ```bash -GET /api/v1/audit/logs?userId=xxx&toolName=xxx&status=completed&limit=50 +GET /api/v1/audit/logs?tool_name=slack_send_message&status=failed&limit=50&offset=0 Authorization: Bearer ``` ### Query Parameters -| Parameter | Type | Description | -|-----------|------|-------------| -| `userId` | string | Filter by user ID | -| `toolName` | string | Filter by tool name | -| `status` | string | Filter by status (`pending`, `completed`, `failed`, `confirmed`, `rejected`) | -| `limit` | number | Max results (default: 50) | +| Parameter | Description | +|---|---| +| `user_id` | Filter by user ID (admins only; non-admin forced to self) | +| `tenant_id` | Filter by tenant | +| `tool_name` | Filter by tool | +| `status` | `pending` `confirmed` `rejected` `completed` `failed` | +| `start_date` / `end_date` | ISO date range | +| `limit` / `offset` | Pagination | -**Response** `200 OK` +Response envelope: ```json -[ - { - "id": "log_...", - "userId": "usr_...", - "toolName": "get_balance", - "arguments": { "account_id": "user123" }, - "result": { ... }, - "status": "completed", - "confirmationId": "cfm_...", - "duration": 245, - "createdAt": "2025-01-01T00:00:00.000Z" +{ + "data": [ + { + "id": "...", + "userId": "usr_...", + "toolName": "slack_send_message", + "status": "failed" + } + ], + "pagination": { + "limit": 50, + "offset": 0, + "total": 1 } -] +} ``` -Logs for tool calls that required human confirmation include a `confirmationId` linking to the confirmation record. - -## Get Single Log - -```bash -GET /api/v1/audit/logs/:id -Authorization: Bearer -``` - -## Statistics - -Retrieve aggregate audit statistics. +## Stats ```bash GET /api/v1/audit/stats -Authorization: Bearer +Authorization: Bearer ``` -Returns counts grouped by status, tool name, and time period. +Returns `{ data: { total, byStatus, avgDuration } }`. diff --git a/docs/content/docs/api-reference/auth.mdx b/docs/content/docs/api-reference/auth.mdx index 25b3fcc..cf50501 100644 --- a/docs/content/docs/api-reference/auth.mdx +++ b/docs/content/docs/api-reference/auth.mdx @@ -1,81 +1,115 @@ --- -title: Auth -description: User registration, login, and authentication endpoints. +title: Auth & Pairing +description: Authentication, profile, and mobile-device pairing endpoints. --- -## Register +## Endpoint Map -Create a new user account. +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `POST` | `/api/v1/auth/login` | None | Verify email/password and issue login JWT | +| `POST` | `/api/v1/auth/verify-credentials` | None | Verify email/password without issuing JWT | +| `GET` | `/api/v1/auth/me` | JWT | Get current user profile | +| `PUT` | `/api/v1/auth/me` | JWT | Update current user profile | +| `POST` | `/api/v1/auth/change-password` | JWT | Change current user password | +| `POST` | `/api/v1/auth/pairing-code` | Flexible auth | Create short-lived pairing token + deep link | +| `GET` | `/api/v1/auth/pair-link/:code` | None | Render HTML redirect page for app pairing | +| `POST` | `/api/v1/auth/pair` | Pairing token | Exchange pairing token for long-lived device JWT | + +## Login ```bash -POST /api/v1/auth/register +POST /api/v1/auth/login Content-Type: application/json { "email": "admin@example.com", - "password": "securepassword", - "name": "Admin" + "password": "secret" } ``` -**Response** `201 Created` - ```json { - "token": "eyJ...", + "success": true, + "token": "", "user": { "id": "usr_...", "email": "admin@example.com", "name": "Admin", - "tenantId": "tnt_..." + "tenantId": "tnt_...", + "roles": ["admin"] } } ``` -## Login - -Authenticate and receive a JWT. +## Verify Credentials (No JWT) ```bash -POST /api/v1/auth/login +POST /api/v1/auth/verify-credentials Content-Type: application/json { "email": "admin@example.com", - "password": "securepassword" + "password": "secret" } ``` -**Response** `200 OK` +Used by management apps that mint JWTs themselves. + +## Current Profile + +```bash +GET /api/v1/auth/me +Authorization: Bearer +``` + +## Update Profile + +```bash +PUT /api/v1/auth/me +Authorization: Bearer +Content-Type: application/json -```json { - "token": "eyJ...", - "user": { - "id": "usr_...", - "email": "admin@example.com", - "name": "Admin", - "tenantId": "tnt_..." - } + "name": "Updated Name", + "email": "new@example.com" } ``` -## Get Current User - -Retrieve the currently authenticated user's profile. +## Change Password ```bash -GET /api/v1/auth/me +POST /api/v1/auth/change-password Authorization: Bearer +Content-Type: application/json + +{ + "currentPassword": "old-pass", + "newPassword": "new-pass-123" +} ``` -**Response** `200 OK` +## Device Pairing Flow + +1. Runtime calls `POST /api/v1/auth/pairing-code` with flexible auth and `peerId`. +2. Gateway returns `{ token, deepLink }`. +3. Mobile app opens `deepLink` and then calls `POST /api/v1/auth/pair` with: ```json { - "id": "usr_...", - "email": "admin@example.com", - "name": "Admin", - "tenantId": "tnt_..." + "pairingToken": "...", + "pushToken": "...", + "platform": "ios", + "deviceName": "iPhone 16" +} +``` + +`/pair` response: + +```json +{ + "token": "", + "gatewayUrl": "https://...", + "peerId": "usr_..." } ``` diff --git a/docs/content/docs/api-reference/confirmations.mdx b/docs/content/docs/api-reference/confirmations.mdx index b46c815..b2f38c5 100644 --- a/docs/content/docs/api-reference/confirmations.mdx +++ b/docs/content/docs/api-reference/confirmations.mdx @@ -1,48 +1,29 @@ --- title: Confirmations -description: Endpoints for managing the human-in-the-loop confirmation workflow. +description: Human-in-the-loop confirmation listing and decision endpoints. --- -## Get Pending Confirmations +## Endpoint Map -Retrieve all pending confirmation requests (polling fallback for SSE). +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `GET` | `/api/v1/confirmation/list` | Flexible auth | List DB confirmations (`status` filter supported) | +| `GET` | `/api/v1/confirmation/pending` | Flexible auth | List in-memory pending confirmations | +| `GET` | `/api/v1/confirmation/:id` | Flexible auth | Get one confirmation by request ID | +| `POST` | `/api/v1/confirmation/:id/respond` | Flexible auth | Confirm/reject with optional reason | +| `POST` | `/api/v1/confirmation/:id/confirm` | Flexible auth | Confirm shortcut | +| `POST` | `/api/v1/confirmation/:id/reject` | Flexible auth | Reject shortcut | -```bash -GET /api/v1/stream/pending -Authorization: Bearer -``` - -**Response** `200 OK` - -```json -[ - { - "id": "cfm_...", - "userId": "usr_...", - "toolName": "transfer_money", - "arguments": { "amount": 1000, "to": "user456" }, - "risk": "high", - "status": "pending", - "createdAt": "2025-01-01T00:00:00.000Z" - } -] -``` - -## Confirm Request +## List Confirmations ```bash -POST /api/v1/confirmation/:id/confirm +GET /api/v1/confirmation/list?status=pending Authorization: Bearer ``` -## Reject Request - -```bash -POST /api/v1/confirmation/:id/reject -Authorization: Bearer -``` +`status` supports: `pending`, `confirmed`, `rejected`, `expired`, `consumed`. -## Respond with Details +## Respond ```bash POST /api/v1/confirmation/:id/respond @@ -55,11 +36,16 @@ Content-Type: application/json } ``` -## Confirmation States +If confirmed, response may include short-lived `confirmation_token` and `expiresIn` seconds. + +## Shortcuts + +```bash +POST /api/v1/confirmation/:id/confirm +POST /api/v1/confirmation/:id/reject +``` + +## Notes -| Status | Description | -|--------|-------------| -| `pending` | Awaiting human decision | -| `confirmed` | Confirmed, request proceeds | -| `rejected` | Rejected, request denied | -| `expired` | Timed out without decision | +- Access is restricted to the initiating user/end-user, or tenant admin with admin role. +- Timeout is recorded as a rejection with reason `Request timed out`. diff --git a/docs/content/docs/api-reference/credential-providers.mdx b/docs/content/docs/api-reference/credential-providers.mdx index 53e945a..e89b171 100644 --- a/docs/content/docs/api-reference/credential-providers.mdx +++ b/docs/content/docs/api-reference/credential-providers.mdx @@ -1,11 +1,20 @@ --- title: Credential Providers -description: Manage credential providers that define how each credential type works. +description: Admin APIs for credential-provider definitions (serviceType, authType, config). --- -Credential providers define the authentication type and configuration for each service. All endpoints require JWT authentication. +## Endpoint Map -## Create Credential Provider +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `GET` | `/api/v1/credential-providers` | JWT + `provider:read` | List providers | +| `POST` | `/api/v1/credential-providers` | JWT + `provider:create` | Create provider | +| `GET` | `/api/v1/credential-providers/:id` | JWT + `provider:read` | Get provider | +| `PUT` | `/api/v1/credential-providers/:id` | JWT + `provider:update` | Update provider | +| `DELETE` | `/api/v1/credential-providers/:id` | JWT + `provider:delete` | Delete provider | +| `GET` | `/api/v1/credential-providers/by-service/:serviceType` | JWT + `provider:read` | Resolve by service type (tenant-first fallback) | + +## Create Provider ```bash POST /api/v1/credential-providers @@ -13,8 +22,8 @@ Authorization: Bearer Content-Type: application/json { - "name": "Gateway API", "serviceType": "gateway_api", + "name": "Gateway API", "authType": "jwt", "config": { "connectUrl": "/auth/connect?service=gateway_api", @@ -23,45 +32,11 @@ Content-Type: application/json } ``` -### Supported Auth Types - -| Auth Type | Description | Config Fields | -|-----------|-------------|---------------| -| `oauth2` | OAuth 2.0 flow | `connectUrl`, `oauth2` (client settings) | -| `api_key` | Static API key | `connectUrl`, `apiKey` (header config) | -| `jwt` | JSON Web Token | `connectUrl`, `jwt` (header name, prefix) | -| `basic` | Basic auth | `connectUrl`, `basic` (header config) | - -## List Credential Providers - -```bash -GET /api/v1/credential-providers -Authorization: Bearer -``` - -## Get Credential Provider - -```bash -GET /api/v1/credential-providers/:id -Authorization: Bearer -``` - -## Update Credential Provider +Supported `authType` values: `oauth2`, `api_key`, `jwt`, `basic`. -```bash -PUT /api/v1/credential-providers/:id -Authorization: Bearer -Content-Type: application/json +## Notes -{ - "name": "Updated Gateway API", - "config": { ... } -} -``` - -## Delete Credential Provider - -```bash -DELETE /api/v1/credential-providers/:id -Authorization: Bearer -``` +- `serviceType` must match `^[a-z][a-z0-9_]*$`. +- Read endpoints redact secret fields from provider config. +- Update merges masked secrets (`"********"`) with existing stored values. +- Tenant scope is enforced by role and tenant context. diff --git a/docs/content/docs/api-reference/credentials.mdx b/docs/content/docs/api-reference/credentials.mdx index 6c9e62d..ee7a255 100644 --- a/docs/content/docs/api-reference/credentials.mdx +++ b/docs/content/docs/api-reference/credentials.mdx @@ -1,11 +1,24 @@ --- title: Credentials -description: Manage user credentials -- store, retrieve, and resolve encrypted credentials. +description: User credential vault APIs, including resolve/check endpoints for runtimes. --- -## Store Credential (JWT type) +## Endpoint Map -Store a JWT-type credential for the authenticated user. +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `GET` | `/api/v1/credentials` | Flexible auth | List current user credentials | +| `DELETE` | `/api/v1/credentials/:id` | Flexible auth | Delete owned credential | +| `POST` | `/api/v1/credentials/jwt` | Flexible auth | Store JWT credential | +| `POST` | `/api/v1/credentials/apikey` | Flexible auth | Store API key credential | +| `GET` | `/api/v1/credentials/oauth/:serviceType/auth` | Flexible auth | OAuth auth URL (currently placeholder) | +| `GET` | `/api/v1/credentials/oauth/:serviceType/callback` | Flexible auth | OAuth callback (currently placeholder) | +| `POST` | `/api/v1/credentials/resolve` | Flexible auth + scope check | Resolve multiple service credentials | +| `GET` | `/api/v1/credentials/check/:serviceType` | Flexible auth + scope check | Check if credential exists for service | + +When using API key auth on `resolve`/`check`, key must include `credentials:resolve` scope. + +## Store JWT Credential ```bash POST /api/v1/credentials/jwt @@ -14,57 +27,58 @@ Content-Type: application/json { "serviceType": "gateway_api", - "token": "eyJ..." + "token": "", + "expiresAt": "2026-04-01T00:00:00.000Z" } ``` -## List User Credentials - -```bash -GET /api/v1/credentials -Authorization: Bearer -``` - -Returns credential metadata (not the encrypted values). - -## Get Credential +## Store API Key Credential ```bash -GET /api/v1/credentials/:id +POST /api/v1/credentials/apikey Authorization: Bearer -``` - -## Delete Credential +Content-Type: application/json -```bash -DELETE /api/v1/credentials/:id -Authorization: Bearer +{ + "serviceType": "slack", + "apiKey": "xoxb-..." +} ``` -## Resolve Credential (SDK) - -Used by agent runtimes to resolve a credential for a specific user and service type. +## Resolve Credentials ```bash POST /api/v1/credentials/resolve X-Api-Key: gk_xxx +X-User-Id: usr_123 Content-Type: application/json { - "userId": "usr_...", - "serviceType": "gateway_api" + "serviceTypes": ["gateway_api", "slack"] } ``` -**Response** `200 OK` - ```json { - "credential": { - "token": "eyJ...", - "serviceType": "gateway_api" - } + "credentials": { + "gateway_api": "...", + "slack": "..." + }, + "missing": [], + "authUrls": {} } ``` -This endpoint requires an API key with the `credentials:resolve` scope. +## Check Credential for One Service + +```bash +GET /api/v1/credentials/check/gateway_api +X-Api-Key: gk_xxx +X-User-Id: usr_123 +``` + +Returns `hasCredential` plus metadata, or `authUrl` when missing. + +## OAuth Endpoints + +`/oauth/:serviceType/auth` and `/oauth/:serviceType/callback` currently return `501` placeholder responses. diff --git a/docs/content/docs/api-reference/mcp-proxy.mdx b/docs/content/docs/api-reference/mcp-proxy.mdx index c078b32..5a415ae 100644 --- a/docs/content/docs/api-reference/mcp-proxy.mdx +++ b/docs/content/docs/api-reference/mcp-proxy.mdx @@ -1,94 +1,87 @@ --- -title: MCP Proxy -description: Proxy endpoints for routing MCP tool calls to upstream servers with policy enforcement. +title: MCP Endpoints +description: Unified MCP endpoint and per-provider MCP proxy (Streamable HTTP). --- -In addition to the protocol-agnostic [agent invoke endpoint](/docs/api-reference/agents#invoke-agent), the Gateway provides a dedicated MCP proxy for JSON-RPC tool calls. This proxy routes tool calls from agent runtimes to upstream MCP servers with policy enforcement, confirmation workflows, and credential injection. All proxy endpoints use API key authentication. - -## Request Flow - -```mermaid -sequenceDiagram - autonumber - participant C as Agent Runtime - participant GW as Gateway - participant Auth as Auth Middleware - participant PE as Policy Engine - participant RP as Request Pauser - participant Audit as Audit Service - participant MCP as Upstream MCP - - C->>GW: POST /v1/mcp/tools/call
X-Api-Key: gk_xxx + X-Agent-Id - GW->>Auth: Authenticate (API key + agent) - Auth-->>GW: User + Agent context - - GW->>PE: Evaluate policy for tool - PE-->>GW: allow / deny / require_confirmation - - alt Policy: deny - GW-->>C: 403 Forbidden - else Policy: require_confirmation - GW->>RP: Pause request - Note over RP: Waiting for confirmation... - RP-->>GW: Approved - end - - GW->>GW: Resolve credentials - GW->>Audit: Create log (pending) - GW->>MCP: Forward + identity + credential headers - MCP-->>GW: Response - GW->>Audit: Update log (completed) - GW-->>C: Response -``` +## Endpoint Map -## Call a Tool +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `POST` | `/api/v1/mcp/mcp` | Flexible auth | Unified MCP JSON-RPC endpoint | +| `GET` | `/api/v1/mcp/mcp` | Flexible auth | Unified session resumption (requires `Mcp-Session-Id`) | +| `DELETE` | `/api/v1/mcp/mcp` | Flexible auth | Unified session termination | +| `POST` | `/api/v1/mcp-proxy/:providerId/mcp` | Flexible auth | Direct per-provider MCP proxy | +| `GET` | `/api/v1/mcp-proxy/:providerId/mcp` | Flexible auth | Per-provider session resumption | +| `DELETE` | `/api/v1/mcp-proxy/:providerId/mcp` | Flexible auth | Per-provider session termination | -```bash -POST /api/v1/mcp/tools/call -X-Api-Key: gk_xxx -X-Agent-Id: -X-User-Id: -Content-Type: application/json +Flexible auth supports JWT, `gk_` API key (+ JWT or `X-User-Id`), and `art_` runtime token. -{ - "name": "get_balance", - "arguments": { "account_id": "user123" } -} -``` +## Unified MCP (`/api/v1/mcp/mcp`) -The Gateway will: -1. Authenticate the API key and resolve the agent -2. Evaluate the policy engine for the tool name -3. Resolve and inject credentials -4. Forward the request to the agent's upstream server -5. Return the upstream response +Supported JSON-RPC methods: -## List Tools +- `initialize` +- `notifications/initialized` +- `tools/list` +- `tools/call` +- `ping` -```bash -GET /api/v1/mcp/tools/list -X-Api-Key: gk_xxx -X-Agent-Id: -``` +Optional query parameter: + +- `providers=id1,id2` to constrain tool listing/calling to specific providers -## Read a Resource +### Example: tools/list ```bash -POST /api/v1/mcp/resources/read -X-Api-Key: gk_xxx -X-Agent-Id: -X-User-Id: +POST /api/v1/mcp/mcp +Authorization: Bearer Content-Type: application/json { - "uri": "resource://example" + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list", + "params": {} } ``` -## List Resources +### Example: tools/call ```bash -GET /api/v1/mcp/resources/list +POST /api/v1/mcp/mcp X-Api-Key: gk_xxx X-Agent-Id: +X-User-Id: +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "slack_send_message", + "arguments": { "channel": "#ops", "text": "hello" } + } +} ``` + +## Per-Provider MCP Proxy (`/api/v1/mcp-proxy/:providerId/mcp`) + +Use this when the caller already knows the provider. + +- Same Streamable HTTP transport behavior (`POST/GET/DELETE`) +- Same policy pipeline for `tools/call` +- Provider-level ACL is checked before forwarding + +## Policy Behavior for `tools/call` + +1. Provider access ACL check +2. Tool policy evaluation +3. Decision: + - `allow`: forwarded upstream + - `deny`: JSON-RPC error returned (`403`) + - `require_confirmation`: connection held until confirmation resolves + +## Session Headers + +For Streamable HTTP sessions, send/receive `Mcp-Session-Id`. diff --git a/docs/content/docs/api-reference/meta.json b/docs/content/docs/api-reference/meta.json index 39dc1af..b08039b 100644 --- a/docs/content/docs/api-reference/meta.json +++ b/docs/content/docs/api-reference/meta.json @@ -1,15 +1,19 @@ { - "title": "API Reference", + "title": "Reference", "defaultOpen": true, "pages": [ "auth", "agents", "mcp-proxy", + "tool-gate", "credentials", "credential-providers", + "tool-providers", + "provider-access", "api-keys", "confirmations", - "audit", - "streaming" + "streaming", + "notifications", + "audit" ] } diff --git a/docs/content/docs/api-reference/notifications.mdx b/docs/content/docs/api-reference/notifications.mdx new file mode 100644 index 0000000..a536db0 --- /dev/null +++ b/docs/content/docs/api-reference/notifications.mdx @@ -0,0 +1,42 @@ +--- +title: Notifications +description: Device registration endpoints for push notifications. +--- + +## Endpoint Map + +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `POST` | `/api/v1/notifications/devices` | Flexible auth | Register or reactivate device | +| `GET` | `/api/v1/notifications/devices` | Flexible auth | List active devices for current user | +| `DELETE` | `/api/v1/notifications/devices/:id` | Flexible auth | Remove device | + +## Register Device + +```bash +POST /api/v1/notifications/devices +Authorization: Bearer +Content-Type: application/json + +{ + "platform": "ios", + "pushToken": "", + "deviceName": "iPhone 16" +} +``` + +`platform` must be `ios`, `macos`, or `android`. + +## List Devices + +```bash +GET /api/v1/notifications/devices +Authorization: Bearer +``` + +## Remove Device + +```bash +DELETE /api/v1/notifications/devices/:id +Authorization: Bearer +``` diff --git a/docs/content/docs/api-reference/provider-access.mdx b/docs/content/docs/api-reference/provider-access.mdx new file mode 100644 index 0000000..0f23048 --- /dev/null +++ b/docs/content/docs/api-reference/provider-access.mdx @@ -0,0 +1,62 @@ +--- +title: Provider Access +description: ACL and tool-policy rule management for user/agent access to tool providers. +--- + +## Endpoint Map + +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `GET` | `/api/v1/admin/provider-access` | JWT + `provider:read` | List rules (with filters) | +| `GET` | `/api/v1/admin/provider-access/by-provider/:providerId` | JWT + `provider:read` | Grouped rules by pattern | +| `GET` | `/api/v1/admin/provider-access/:id` | JWT + `provider:read` | Get one rule | +| `POST` | `/api/v1/admin/provider-access` | JWT + `provider:update` | Create rule | +| `PUT` | `/api/v1/admin/provider-access/:id` | JWT + `provider:update` | Update rule | +| `DELETE` | `/api/v1/admin/provider-access/:id` | JWT + `provider:update` | Delete rule | +| `POST` | `/api/v1/admin/provider-access/evaluate` | JWT + `provider:update` | Dry-run tool policy evaluation | +| `GET` | `/api/v1/admin/provider-access/agent/:agentId` | JWT + `provider:update` | List agent rules | +| `PUT` | `/api/v1/admin/provider-access/agent/:agentId` | JWT + `provider:update` | Replace all agent rules atomically | + +## Rule Schema + +```json +{ + "subjectType": "agent", + "subjectId": "agt_...", + "providerId": "provider_...", + "action": "allow", + "toolPattern": "slack_read_*", + "riskLevel": "low", + "description": "Read-only Slack" +} +``` + +- `subjectType`: `user` or `agent` +- `action`: `allow` `deny` `require_confirmation` +- `riskLevel`: `low` `medium` `high` `critical` +- `confirmationMode`: optional `always` or `never` + +## Dry-run Evaluate + +```bash +POST /api/v1/admin/provider-access/evaluate +Authorization: Bearer +Content-Type: application/json + +{ + "userId": "usr_...", + "providerId": "slack-provider-id", + "toolName": "slack_send_message", + "agentId": "agt_..." +} +``` + +```json +{ + "action": "require_confirmation", + "risk": "high", + "matchedRule": { + "id": "..." + } +} +``` diff --git a/docs/content/docs/api-reference/streaming.mdx b/docs/content/docs/api-reference/streaming.mdx index 8f4f336..52c21fd 100644 --- a/docs/content/docs/api-reference/streaming.mdx +++ b/docs/content/docs/api-reference/streaming.mdx @@ -1,65 +1,48 @@ --- -title: SSE & Real-time -description: Server-Sent Events stream for real-time notifications and confirmation workflows. +title: Streaming (SSE) +description: Real-time confirmation events over Server-Sent Events. --- -The Gateway provides a Server-Sent Events (SSE) stream for real-time notifications, primarily used for the human-in-the-loop confirmation workflow. +## Endpoint Map -## SSE Stream +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `GET` | `/api/v1/stream` | JWT | Open SSE stream | +| `GET` | `/api/v1/stream/pending` | JWT | Polling fallback for pending requests | -Connect to receive real-time events: +## Open SSE Stream ```bash GET /api/v1/stream Authorization: Bearer +Accept: text/event-stream ``` -This endpoint returns an SSE stream (`text/event-stream`). Events include: +The stream sends: -### Event Types +- `connected` (initial handshake) +- `CONFIRMATION_REQUIRED` +- `CONFIRMATION_RESOLVED` +- `heartbeat` (every 30s) -| Event | Description | -|-------|-------------| -| `CONFIRMATION_REQUIRED` | A tool call requires human confirmation | -| `CONFIRMATION_RESOLVED` | A confirmation was confirmed or rejected | -| `AGENT_INVOKED` | An agent was invoked | +### Example Events -### Example SSE Data +```text +event: connected +data: {"userId":"usr_...","timestamp":"..."} -``` event: CONFIRMATION_REQUIRED -data: {"id":"cfm_...","toolName":"transfer_money","arguments":{"amount":1000},"risk":"high"} +data: {"id":"...","tool":{"name":"transfer_money"},"risk":{"level":"high"},"timestamp":"..."} event: CONFIRMATION_RESOLVED -data: {"id":"cfm_...","status":"confirmed","confirmedBy":"usr_..."} +data: {"id":"...","confirmed":true,"timestamp":"..."} ``` ## Polling Fallback -For environments that don't support SSE, use the polling endpoint: - ```bash GET /api/v1/stream/pending Authorization: Bearer ``` -Returns all current pending confirmation requests as a JSON array. - -## Client Usage - -### Browser (EventSource) - -```javascript -const eventSource = new EventSource('/api/v1/stream', { - headers: { 'Authorization': `Bearer ${token}` } -}); - -eventSource.addEventListener('CONFIRMATION_REQUIRED', (event) => { - const data = JSON.parse(event.data); - console.log('Confirmation needed:', data.toolName); -}); -``` - -### Dashboard Integration - -The Gateway dashboard uses the SSE stream to show real-time confirmation cards. When a `CONFIRMATION_REQUIRED` event arrives, it displays a decision card with tool name, arguments, and risk level. +Returns `{ data: [...] }` with pending confirmation payloads. diff --git a/docs/content/docs/api-reference/tool-gate.mdx b/docs/content/docs/api-reference/tool-gate.mdx new file mode 100644 index 0000000..987aa70 --- /dev/null +++ b/docs/content/docs/api-reference/tool-gate.mdx @@ -0,0 +1,54 @@ +--- +title: Tool Gate +description: Policy-evaluation and audit-report endpoints for non-MCP agent runtimes. +--- + +`/api/v1/tool-gate` is useful when your runtime does not proxy MCP directly but still wants gateway policy + confirmation + audit handling. + +## Endpoint Map + +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `POST` | `/api/v1/tool-gate/evaluate` | Flexible auth | Evaluate decision (`allow`/`denied`/`confirmed`/`rejected`/`timeout`) | +| `POST` | `/api/v1/tool-gate/audit` | Flexible auth | Report execution result and complete/update audit record | + +## Evaluate + +```bash +POST /api/v1/tool-gate/evaluate +X-Api-Key: gk_xxx +X-Agent-Id: agt_... +X-User-Id: usr_... +Content-Type: application/json + +{ + "toolName": "stripe_charge", + "providerId": "stripe-provider", + "params": { "amount": 1200 } +} +``` + +Possible responses: + +- `{ "decision": "allow", "risk": "low", "auditId": "..." }` +- `{ "decision": "denied", "risk": "high", "auditId": "..." }` +- `{ "decision": "confirmed", "confirmedBy": "usr_...", "auditId": "..." }` +- `{ "decision": "rejected", "reason": "...", "auditId": "..." }` +- `{ "decision": "timeout", "reason": "Request timed out", "auditId": "..." }` + +## Audit + +```bash +POST /api/v1/tool-gate/audit +X-Api-Key: gk_xxx +X-User-Id: usr_... +Content-Type: application/json + +{ + "auditId": "...", + "result": { "ok": true }, + "durationMs": 240 +} +``` + +If `auditId` is omitted, `toolName` and `providerId` are required and a new audit record is created. diff --git a/docs/content/docs/api-reference/tool-providers.mdx b/docs/content/docs/api-reference/tool-providers.mdx new file mode 100644 index 0000000..101b9a9 --- /dev/null +++ b/docs/content/docs/api-reference/tool-providers.mdx @@ -0,0 +1,44 @@ +--- +title: Tool Providers +description: Admin APIs for MCP tool-provider routing configuration. +--- + +## Endpoint Map + +| Method | Path | Auth | Purpose | +|---|---|---|---| +| `GET` | `/api/v1/admin/tool-providers` | JWT | List providers | +| `POST` | `/api/v1/admin/tool-providers` | JWT + `provider:create` | Create provider | +| `GET` | `/api/v1/admin/tool-providers/:id` | JWT | Get provider | +| `PUT` | `/api/v1/admin/tool-providers/:id` | JWT + `provider:update` | Update provider | +| `DELETE` | `/api/v1/admin/tool-providers/:id` | JWT + `provider:delete` | Delete provider | +| `GET` | `/api/v1/admin/tool-providers/:id/tools` | JWT | Fetch upstream `tools/list` | + +## Create Provider + +```bash +POST /api/v1/admin/tool-providers +Authorization: Bearer +Content-Type: application/json + +{ + "name": "Slack MCP", + "pattern": "slack_*", + "endpoint": "https://mcp.example.com/mcp", + "authType": "bearer", + "authSecret": "...", + "priority": 100, + "description": "Slack tools" +} +``` + +`authType` values: `bearer`, `api_key`, `none`. + +## Fetch Provider Tools + +```bash +GET /api/v1/admin/tool-providers/:id/tools +Authorization: Bearer +``` + +Returns normalized tool metadata from upstream MCP `tools/list`. diff --git a/docs/content/docs/authentication/api-keys.mdx b/docs/content/docs/authentication/api-keys.mdx deleted file mode 100644 index 90a0b91..0000000 --- a/docs/content/docs/authentication/api-keys.mdx +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: API Key Authentication -description: Gateway API keys (gk_) for server-to-server trust between agent runtimes and the Gateway. ---- - -Gateway API Keys (`gk_`) provide server-to-server trust. They are used by agent runtimes to call back into the Gateway for tool calls and credential resolution. - -## Creating API Keys - -Admin users create API keys with specific scopes: - -```bash -POST /api/v1/admin/api-keys -Authorization: Bearer -Content-Type: application/json - -{ - "name": "Agent Server", - "scopes": ["credentials:resolve"] -} -``` - -Response: - -```json -{ - "id": "...", - "key": "gk_xxx...", - "name": "Agent Server", - "scopes": ["credentials:resolve"], - "keyPrefix": "gk_xxxx" -} -``` - -**Important:** The full API key is only returned once at creation time. Store it securely. - -## Available Scopes - -| Scope | Description | -|-------|-------------| -| `credentials:resolve` | Resolve credentials for a user | -| `credentials:read` | Read credential metadata | -| `credentials:write` | Store/update credentials | - -## Using API Keys - -When an agent runtime makes requests through the Gateway, it sends three headers: - -```bash -POST /api/v1/mcp/tools/call -X-Api-Key: gk_xxx -X-Agent-Id: -X-User-Id: -Content-Type: application/json - -{ - "name": "get_balance", - "arguments": { "account_id": "user123" } -} -``` - -| Header | Purpose | -|--------|---------| -| `X-Api-Key` | Proves the caller is a trusted server | -| `X-Agent-Id` | Identifies which agent is calling (the Gateway resolves the agent record for routing) | -| `X-User-Id` | Identifies the end-user on whose behalf the call is being made | - -## Key Storage - -API keys are stored as SHA-256 hashes. The Gateway only stores a prefix (`gk_xxxx`) for identification. The full key cannot be recovered after creation. - -See the [API Keys API Reference](/docs/api-reference/api-keys) for management endpoints. diff --git a/docs/content/docs/authentication/index.mdx b/docs/content/docs/authentication/index.mdx index 06e2eda..1274421 100644 --- a/docs/content/docs/authentication/index.mdx +++ b/docs/content/docs/authentication/index.mdx @@ -1,22 +1,23 @@ --- -title: Overview -description: Dual authentication model supporting JWT and API key authentication. +title: Authentication Overview +description: JWT, API key, and runtime-token authentication modes in the gateway. --- -The Gateway supports two authentication methods, each designed for different use cases. +The gateway supports three authentication modes, each optimized for a different caller type. ## Authentication Methods | Method | Format | Use Case | |--------|--------|----------| -| **JWT** | `Authorization: Bearer ` | Admin operations, agent invocation from frontend | -| **API Key** | `X-Api-Key: gk_xxx` + `X-User-Id` | Agent runtime to Gateway (tool proxy, credential resolution) | +| **JWT** | `Authorization: Bearer ` | Human-facing app flows and admin operations | +| **API Key** | `X-Api-Key: gk_xxx` (+ JWT or `X-User-Id`) | Server-to-server trust for runtimes and integrations | +| **Runtime Token** | `Authorization: Bearer art_...` (or `X-Api-Key: art_...`) | First-party registered agent runtime identity | ## When to Use Each ### JWT Authentication -Use JWT for **human-initiated operations**: +Use JWT for human-initiated operations: - Logging into the dashboard - Managing agents, credential providers, and API keys @@ -25,15 +26,22 @@ Use JWT for **human-initiated operations**: ### API Key Authentication -Use API Keys for **machine-to-machine communication**: +Use API keys for machine-to-machine communication: -- Agent runtimes calling the tool proxy -- Credential resolution from agent code -- Automated server-to-server integrations +- Calling MCP endpoints from trusted backend services +- Credential resolve/check flows +- Service integrations that need scoped gateway access -## Learn More +### Runtime Token Authentication (`art_`) - - - - +Use runtime tokens for registered first-party agents: + +- Issued once at agent creation (and on token rotation) +- Validates agent identity directly +- Works with `X-Gateway-Session-Token` to preserve end-user context in callback flows + +## Reference Links + +- Auth and pairing APIs: [/docs/api-reference/auth](/docs/api-reference/auth) +- API key management APIs: [/docs/api-reference/api-keys](/docs/api-reference/api-keys) +- MCP endpoints auth behavior: [/docs/api-reference/mcp-proxy](/docs/api-reference/mcp-proxy) diff --git a/docs/content/docs/authentication/jwt.mdx b/docs/content/docs/authentication/jwt.mdx deleted file mode 100644 index ea8bde9..0000000 --- a/docs/content/docs/authentication/jwt.mdx +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: JWT Authentication -description: JSON Web Token authentication for admin operations and frontend agent invocation. ---- - -Users (Agent Creators) register and log in to receive a JWT. The Gateway can also verify JWTs issued by external identity providers via JWKS. - -## Registration - -```bash -POST /api/v1/auth/register -Content-Type: application/json - -{ - "email": "admin@example.com", - "password": "...", - "name": "Admin" -} -``` - -## Login - -```bash -POST /api/v1/auth/login -Content-Type: application/json - -{ - "email": "admin@example.com", - "password": "..." -} -``` - -Response: - -```json -{ - "token": "eyJ...", - "user": { - "id": "...", - "email": "admin@example.com", - "name": "Admin" - } -} -``` - -## Using JWTs - -Include the JWT in the `Authorization` header for all authenticated requests: - -```bash -GET /api/v1/admin/agents -Authorization: Bearer eyJ... -``` - -## JWT Claims - -The JWT payload includes: - -| Claim | Description | -|-------|-------------| -| `sub` | User ID | -| `email` | User email | -| `tenantId` | Tenant ID for multi-tenancy isolation | -| `iat` | Issued at timestamp | -| `exp` | Expiration timestamp | - -## Configuration - -Set the JWT secret in your environment: - -```ini -JWT_SECRET=your-secret-key -``` - -See the [Auth API Reference](/docs/api-reference/auth) for full endpoint details. diff --git a/docs/content/docs/authentication/meta.json b/docs/content/docs/authentication/meta.json index d85d303..5c025c2 100644 --- a/docs/content/docs/authentication/meta.json +++ b/docs/content/docs/authentication/meta.json @@ -1,5 +1,5 @@ { "title": "Authentication", "defaultOpen": true, - "pages": ["index", "jwt", "api-keys"] + "pages": ["index"] } diff --git a/docs/content/docs/getting-started/docs-mcp.mdx b/docs/content/docs/getting-started/docs-mcp.mdx new file mode 100644 index 0000000..1c5a449 --- /dev/null +++ b/docs/content/docs/getting-started/docs-mcp.mdx @@ -0,0 +1,105 @@ +--- +title: MCP Server +description: Use the Simplaix Gateway docs as an MCP server inside Claude Code, Cursor, and other AI editors. +--- + +The Simplaix Gateway docs expose an **MCP (Model Context Protocol) server** so you can query documentation directly from your AI editor. Ask questions, look up API endpoints, and get accurate answers grounded in the actual docs — without leaving your editor. + +## MCP Server + +The MCP server is hosted at: + +``` +https://docs.simplaix.com/api/mcp +``` + +### Available tools + +| Tool | Description | +|------|-------------| +| `list_docs` | List all documentation pages with titles and descriptions | +| `search_docs` | Search pages by keyword | +| `get_page` | Fetch the full content of a page by slug | + +--- + +## Setup + + + + +### MCP config + +Add the following to `.mcp.json` in the **root of your project**. Claude Code automatically picks up this file. + +```json +{ + "mcpServers": { + "simplaix-gateway-docs": { + "type": "http", + "url": "https://docs.simplaix.com/api/mcp" + } + } +} +``` + +To apply globally instead (all projects), add the same block to `~/.claude/settings.json` under `"mcpServers"`. + +### Agent skill + +Save this as `.claude/agents/gateway-docs.md` in your project to give Claude a dedicated `/gateway-docs` slash command: + +````markdown +--- +name: gateway-docs +description: Look up Simplaix Gateway documentation. Use this when asked about Gateway concepts, API endpoints, configuration, or integration guides. +tools: + - mcp__simplaix-gateway-docs__list_docs + - mcp__simplaix-gateway-docs__search_docs + - mcp__simplaix-gateway-docs__get_page +--- + +You have access to the Simplaix Gateway documentation via MCP tools. + +When the user asks about Gateway concepts, APIs, or how to integrate: +1. Use `search_docs` to find relevant pages +2. Use `get_page` to read the full content +3. Answer based on the actual documentation + +Always cite the page title and URL when referencing documentation. +```` + + + + +### MCP config + +Add the following to `.cursor/mcp.json` in the **root of your project**: + +```json +{ + "mcpServers": { + "simplaix-gateway-docs": { + "url": "https://docs.simplaix.com/api/mcp" + } + } +} +``` + +To apply globally instead, add it to `~/.cursor/mcp.json`. + +Once configured, Cursor's Agent mode will call the MCP tools automatically when you ask questions about the Gateway. + + + + +--- + +## Example prompts + +Once configured, try asking your AI editor: + +- *"How do I authenticate with the Gateway?"* +- *"What headers does the Gateway inject when forwarding to an agent runtime?"* +- *"Show me how to set up a credential provider for OAuth2."* +- *"What's the difference between an API key and a runtime token?"* diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json index bd60313..88df9b8 100644 --- a/docs/content/docs/meta.json +++ b/docs/content/docs/meta.json @@ -4,6 +4,7 @@ "---Get started---", "getting-started/index", "getting-started/quick-start", + "getting-started/docs-mcp", "---Guides---", "guides/agent-creator", "guides/app-builder", diff --git a/docs/package.json b/docs/package.json index 18e28d3..49cc92e 100755 --- a/docs/package.json +++ b/docs/package.json @@ -10,17 +10,20 @@ "postinstall": "fumadocs-mdx" }, "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.2", "fumadocs-core": "16.5.1", "fumadocs-mdx": "14.2.6", "fumadocs-ui": "16.5.1", "lucide-react": "^0.563.0", + "mcp-handler": "^1.0.7", "mermaid": "^11.12.3", "next": "16.1.6", "next-themes": "^0.4.6", "react": "^19.2.4", "react-dom": "^19.2.4", "react-icons": "^5.6.0", - "tailwind-merge": "^3.4.0" + "tailwind-merge": "^3.4.0", + "zod": "^4.3.6" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.18", diff --git a/docs/public/claude-code-skill.md b/docs/public/claude-code-skill.md new file mode 100644 index 0000000..6d24fd1 --- /dev/null +++ b/docs/public/claude-code-skill.md @@ -0,0 +1,17 @@ +--- +name: gateway-docs +description: Look up Simplaix Gateway documentation. Use this when asked about Gateway concepts, API endpoints, configuration, or integration guides. +tools: + - mcp__simplaix-gateway-docs__list_docs + - mcp__simplaix-gateway-docs__search_docs + - mcp__simplaix-gateway-docs__get_page +--- + +You have access to the Simplaix Gateway documentation via MCP tools. + +When the user asks about Gateway concepts, APIs, or how to integrate: +1. Use `search_docs` to find relevant pages +2. Use `get_page` to read the full content +3. Answer based on the actual documentation + +Always cite the page title and URL when referencing documentation. diff --git a/docs/public/logo.png b/docs/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3b53f6840d0c70603dfaba804be0b3fc9728ad GIT binary patch literal 27509 zcmXt9Wmr>hA0A_L4Uz5;klc{2Q347AiiC71FlmtPZczal-QCjNAl;2Lqr3av|HJ!X z+qtfDUFST{&Ut>%{ktPfO+|qKmkJjE01&)Ul>31CZTs(ojfwjHuxNz^0Du8+( zcc1yi1flXVb?=FjL&JsbBEp5GL7%aouC{QONu;7Olh;>{MQ7@-8m7Je%{)jL0`(qY zFia{4<~?xKH|)r;VPKOS03+wOz`w>wmyTI(b!`P)yQNE;rla9W1>`pRoM=!S<4KV+ z-Ai|N`5FSKSpPi~m3E+x3c;&HRILA92eNZ$=tPP-QVR;jB)ulzYe$_H&p%e6V*2ki zxZu#R5)0iM?Kr)I9v=GNc*^~q*5O24Ff0IE?lKYk1N@6uGy>mMRY zqR?6alRcrZf9a4Fa$n5XHcuv&rfs}{YUo<`c?i8X#dVp_1Lrs7V8SYJ-1yC#H_`?) zp$2j%wr;`_` zA+Ef(iYS;V)nC|-_PIB$G*9!YS>K5b>k&-qoDjQRKm0;(pNTwFi609zDoA>>(`56By5L$fB(7~-JEU` z<>cl{244C=goLi7`)$M>k;)^Zqoc>PV$S@9y)UmD8vv-G*r9=@swoH#<=2JUi^Kf; zJen`2b(tj}&L`xm?gQ6*qkBo8KaUi4+MP`A=#Xi*3pBZJ@w#J8e<1O>TUh>X!nS>P zmiE*{3xBdv)7mhU@oEL=0mp}@HGTo-SLMc z8QIwK6x`h0Dp}tp)=>%B?91#4NjUFo_v$w~M@~WpGUW~B-<8|kZ5IHL=tW=JuFHswFuT@mH}8^Z2bM<}^W7;-(htU_or zEBILtHs1g7zBh~y`EO24kDA5W#ET`mwW0a>`H~AY*3=#0*86xKiUkn*{>JMq*#1`PZYR)FFxj8ukIrKu-_h|$FEv(&Osk5yC8fm4F&isGIvp9j#jz7HEU%hhVNNl7BjsneW z1Fw3crputfNbZH@oD?Zv-#QChfO;d9z}IZSQ;@*Gz`)*6{R*pd3>GAsF84sLgD&}n z4$r4QEI)3&8T0-AHL#&xub;vJb3mwUV$1bfL`wNcF9S$@$?NK$u8{RSKUxV4Klcdj zmBRZn%F(|E&RBrdrM_1~Wutw?bS|gQ$5s5v?%#9jCfa#t z&{DGQ@f>V$=d;CoX^Mj1*41dA;m$)STfOL1jr0xurfJezs8wdNzK8Fj3-y@#!~bAP@FnYc#=jx}_CB!-w{EAxmghbpCB z$g~8#6_b*fK$@5-NPWz$k|LN?NpI*^mk4^xY3LNIshDV0+e4UPV5$6Pn5HSDQudu$ z=>#}*;aHhqU2oieI9X&qVRDeIO+vv^xkGx;lK+gxug%%*uzri`?(?w{O&2eKfiM`q zEs8P_X%#*I1(M_unaPW{JYbC#6CI%{=d;9C*x)qop(V%|J^Ls8tFxB?lAqvkW1}L7=vw0l_@wveic~A%Rz1J!RHIbfFQ!*zg%$RLHj^^3l}M}ug!CkOSnB;J z?SHCSsz+HM@P>0DZ6;NC$|kE9HcOi22VJpZG6u>Bul)VVT#_P^Pge)ck#^Paq-p}R zQTP>Umf)^W3sObF=yDST9g%}#>gwvYe}{$+qBSkCCaQgjt6zaSzHPBEJRk}()nao} zanP+LWLCN~SREDN(CTz|>h7uNn6f%4IK>i3!3lKEXd6>y6H4Q8fIi6xe!{Vhu~2qK5Datyg!@P={Yeqx+mh#$hXiLTVWiM0~RZ`>D-p0B&Oc zqVTOINlG%BPzj3Dl>nle{pD7lr0wnPOaI{%vD%2RFnH3BAK-SuIT{OK=5U|hhGECn zUJ@kFH!szlW215T!3KoC?DlYb@n_Fg%JHA~@k)D=@B1z>sVZ=l7X#Id=hZH0rUwFy z$uF@Rjj!X||K$o8tz6?>&;MoG26lkf!$yRh7zT!=Y#M z3_Lb70@Wtd8vS*jQH%7N4sokX60%(aZTktap;emCqc-Eib$KB|lgkWgY6^p;v>r$T zKbN0Gqv>F;r^I}!KNWB#b}VFyxD%IRV!U_`gNGxhk5o(IB16q-(!d^CIOB`@@d z>#SIkZKt`oRxV}?i7;PUi)0gY@-!1?iUaKF0<@#wKeAiX`x4ub11m^9Q1z-9LnjCN z<>LGEi-HzEh;!C7M2@X$!_G#RjTz)(^lBLwUp}Db%Xe+UcTUWfkv*PKZabYaNNakw z5(huN6mhoCLp%t%1CGC(Z2EHA4C(I})i8hP8+W{JiRzHd;`a8IuYf_sqKT67p zg$0s!YXKZy_>+s)_I%n^%(9udgeIbId~-~JKI;0S%;%qSjo8B^&hor;paQ6l#H$B* zGU_H=x7RV-k-{k~tnGzqdRz7Ro?=){2)xu^`i;}6mrJj%oUKuMkMflnzsaG&y6p62 zHN7ptJ~EdNzhsFZFuILQ`EP%J`0Fz#3~6g$c5G4%;DSG*#g+Hjc`@D%Ix{o#g5A!? zBQd{Qd^|i$EWu#eK=^Ets$)bE*0emd%!07y9py5ej;v#SLK5QlE)O2#FGN2gx?VS5 z+>ayd24tcg1l;VLdj{|i=evb1)mSfZH+x(ejMdsMlOp_+jAFB}6)EE_NvS5_Wx_Y9 z6#|53e|p)smGwQ4S%SbXg{d;WFYBxoq%1nLO%?c0)DS)#bS1sUJ#ffAc3rgueu)CF zg@Zl4asqPD1>&llUUtG>_c$m()3lvsZ=Jdu<<@5mmXlVWuj+kHHTY=*sS0~Im!=_N zM#u&_1E-pwt&pODiKX6BC{IUV5Teg@j!HE%7V<>NNXxTpkCV;e6O5rT?x;#IegiLj zZA5{O=?H-D3Pb3^xySOIK~Z-Exl8}xAdRnwqj=(p8O54P&DMz&FGb-CR#gUkj*)P< z3HH~FX(qeS&Zel70jDpQ)=#kU~8!_rdeHSK~`x4bVq#rAH*L^c+ zG&0Wgjt%5u=d4z%(hzADWk%^(RsAzQf4CI#OW<>9S|CR@uj$+QPx^UudnehX@yAm8 zZ?W};d0d-Nhz&kucx%D>$SNsy4jB>%0_9-9lajmcTaItx(_6^ zEpgK{v23H_0>9Nu>rOFvaCdq^+Qu56d@ZEhum|&cOYhbz68tZlse`9Goln>UcvkW- z-mdF;t_FWfsxT!;Ujo@t)>_V#N*7neA(C!(ZgQwh`3O3*WQP3e3sppF_ZsiccPf8X z+p;}@9)3g0-cFqfBwJ=&@L?f%DTbEOZ zMKp4oMA5t&HuZgPlGYoKeC78du1r~KyqUa~LLog_e!=B?+jCwLxy2=OJ?SOT#8Ewo@G17^!yZytL89dlhf!pgTV6a@1TkF`72iZl#UI22S4F+go-xTd^+R zj@o8tmYinvBSHwMYrtsQ-vQZ`%=;j%5I?5du>`+W4*kRcUvX-6!{-*_uO-q&-?D$} z7ZA!xh>5t2N`I4Y*Y+KPE!thltimWkV@B32Gnepnh29gh`Lv&Nb@otyPC#CI=48d5 z;W{$+2z*iWD8K{n42dc2F9Hc|P7WwA?ngyN>h*GaytT60SJ~EYyB*LtdMgzbG`$6q z3Zx!AgGMm~vMh(@Llrsg#?cs1Fd833m5~C(u9nqKY49}G#guLWV=qQQSxwTGDm`yb zLvULRPj*WGSeZ4^p6U;S7W(7r&0MD zawusl^JIpQir<1P@_}yfo-xoch~YErb~}qL&HHwT56wx%>1atm_r2)Dpw&d~kVi6s zv+~0pv(1Q&s&~>*_nloG6NU^!^D?0qHonH?UhH*NuZ^ zWGLAmJZiLC>z^SOkVYsajK387N|&G|U9WZ+iv32sE;4Hvd>bxhkKsR1X_0dRO2oQg z0mpTzR_0n4+oJ91hIcYIJMB%^Z^Wx>ZOVB)6;zDg($M**p-Fc$1iR_nY|CxOu4VP17zX%iEZn>zb-T<^!TG;^QxF$K$`mb=3j>Uk>qvz6u}EEJzf zuNNZS>gby`)LSFnKKk=!@+7q)nMRvpKM9a`mYrg7IGz^OerbvG1zqx6pAnY>o%^1p zCkHgF(yOzBhnAlOjmMoO-`}pc74ZA8%?HY`HMlIeW=qiXrPLukg55SMzDvA+{~m7f z@WSHh8{z03o%i)>%svmP$|fhV`#Zp>bKaOY76gq@rf$NjtVfQalkhczA(ouQ2V
UxS9k({5}mP<;cBq}QuA*cYI$uxXJn$4^cCu)XhcZ|Zkc zTa7BAp&l|(2iX}O7#B*P1_Z4h-HOejjw%Z2L=PKmuzGf?B3@g6Es$@whQsJfz@Jf* z1Svw0I0kfB5iAusfEM~e<`bRVveJWcs28>wA28W=RbwfIjJNgPA%v7u6JJq9g>LAJ zYDaqj7Rg-hI>Rh@@aHgtVQGE(t{gEs9%4YGQ7b_zXmF@1X`b_NFhOnpj6c#YHpK?3 z{P%%)j$x~}(x}})?z^NKlhchp-7&j*g!tE0g71-%tW`(u2wVDZU(ygEz zY>j8RV|+c@1eIoRWkkNBBLF`-k=ImvMg;{T_~O{qlEo+b&+YLWtTq=bf<<`0G!bCi z5;kfuUI5j&h}mNe*kgY#?tXu5N(3mQwXki>za>$d>AVlu+I*up0)~}}$j($fKHTNm zA?HE?^qy;BY%1@a-wVG_bMkALz6b|= zdYaUkb+478>1jb*Dgr?fogg)ThIW&CVqeied;|)JW<8Q5tdIWNN>G70;R3>6ppeH& zx6NG>Wc|7FetB)MlfP9ow-j2x)=#b|yghSj{CuKDMMS6a8Ixw4c<-o%E@IDfMZmd~ zXzZI(L2Ha?kcC&{N~V4a_D~=vT-R@}Ch+Dc;3R%sG$jnvtI_3X$x-ChUoO#p(E+p+ zqGF%p{e(OuuaVUPBO)efqoVlj%XpLh6E36#(LtJ==PPCRK>LA$bhFdv7Z~OwCxhJw zvlXV;x$mDpC?-&hfNLc+>I-vX=G=XpbIv*bS+4Y`e(U`v@7bt~K#c;J zrfszd=h91fK7WBN&6Drj<^J6I$)0N4;Li|qe(Sq7S8Ikp_hXlsn9y3Oy(}UOdH&6I z3&M7;<(m)9yI-DR;}G7M&lZFyIPy3y<4HG+DS-M2GBeoNf#SMD($rfL~_T?7)= zo!j@xY3io0-E5`7ZaVOI_dSpa=7LK;PY*94d(ZYAq5E%%*V8*`=sk@=vItI^&4bHzV<)OXd6Wma+EFA^qcV2r)Zi3G#rA@S!ot^oF!9 zSrL;_P*9{xPNj*K(rG1nF6cvYR3NQf6yDs4umoCp1KtB3ttjM-Cx!tDz=`l96F|fw zZ$#vI3)-LM4Tft;-FvH=yJ)s|iRaU!8M4cP8B4NRxfPtrm<8rg!le|cMVp49*iWy} zGR4b{JA<)^0T0;CL?GM{N-Ak>p>;~r2sklj?#iEUQc}Jt=tdOryAZa82FsZM*UNE^ zps&$2E)~{cH927ZGKFZmguy4%&|5;Y5&Yd8fJn0RxpwC#M;Co$+OZ!Qw|rK==^xyf zHFUkgqpyS#9+!)4$tsZg56aIElxtY$5$T-SgTq+G>Oh7hAMyhjy`<;0OzgP)u{ZXR ztr(A>5A*JYNpaYIZdi6mmiD|l8@A_I%7Wb+IpCMZg5P=2K+gHMckz&3ee6AdM%iF4 z8SwafCMXegGW&vC8Wf zMP#iXyfPma55_FaxT#OEIuD?~Pk&f`ysY)pD}9lEV=MD&hcv*wIFmj!3Ugg zmFwiK*RKytYL|2Zr3R|^$u@2VXAx+t`!(Jh%^lTL6>VFng|Qhx5QY=<4&&rUTA2y=Y7&mo&|Yvw=Pc6?Wc9b zv>S1(FeVAgSslCYNU`e?X2p+H+U~k0#qN9G^H+{tE8|$x&byDr3qf0f9U*n5c2R`e z!~@Xb{#zIb2m)Kc@NE4j z8G(a@Qs`!|cu@NO$`3K3?pJBtETw=(aG^Y#H^wFf-G+eOC%Y`}CUCE5OAIZI>)@pms2 zi!S+{OyW&T?>;fKj*&nC-6`lq!Fuk`CaZGF8;G|!h!Pjgy(8Tk;BgTe$^aNE?eBkU zY8UlFEma09xhgcTZXV~kRw_0pLm8+?_&FUN&p|cz(36OxPM344sPq18WbKl>Wi(RK zY!G2Ge~X&3Sx(iZQ0vVQiEYzKj{$`!iI~R=Di>n&0=Ge-g0F`n`=Jf*P(xb6md$lI zsllYb*96V+!^BD>UVUVVT%w2haM=u3T;G_2zY9i^_(ST0V5_gwg|AN#S{oWN5w9X0 zlc7Qtjl~_S70-+Z<-nz1hsvt4Jy`)%#Z-!x*i~MM=2%FgwYM!VK|9#Q^1ichi<~Fh zr^g2uyJK3FCW25xnY|rf%fatb+VS845{^Ugn*kO?>6a!1z{YjuZl}@q81xB8CE}P_dJ4Zy>`tJ ztdyk-4!d$XTy!G_HiF4335ONR4+h#@0GsU)510LWoc%(a{Zi&omuv!Redb0;V4wax zwEZVk5+nB}j;Lj03-<28YOs)HF*!n5EiX&zvN9mv0$+-VWj9m3gQYH0u}I=N?inBn zOVKIIDN(9Q6XK2^GLLhH!t1|&{o>n1RYp?=dDsscf~-9!`pXC?lv{b17yzyEZDCQS zD@VI_I1QUuaC$9{ZEI_qb>x7!W0`lfm6PkjRZgvMBZnwG6WnizH}rPvMX6DHdQIYa zqfPU>C22*QN{;S3Iq|q6VEP`XhKMr_QAfiLccdM~1BF>KfqL$rfG_Ro^G@9eQuahe z=U~4TFMj7%gR^mRl6-+{2yfysm!1aJCXUPG&pVB}AIK3Sg*RUY<0304lWC1rx|2Ee z_LEl;7VZUFA-pZunR6)MvwJ>U&1>h`4FU{-)3w6NK2^$>4h>seSG0M}-d;J3Hop`S zs)d=U;bD(bU{-HA2YQavC4C&^W_hW~7-*04Q5EjDE1X`n%kK%R1CH?8; zf~_jr;p;n95zp&mwZL@FzqSMpvQjPBGUL|DyjJby@}TaQkfm5 z{d38yXjGMAzh%xR){x08En~k{1{g`XxllPy(TC?+W2|}5i8*r-))9REhi-D7@1XT# z33yPXt5-R}7)k7R8O$DP1WfnzLv=qNb<*p0MQ(7>y!x*>0!I~$-QBn}?Xc4Va4wcEaV!O4rBw1M1ax6y2i8 zJ%0+4kmzV*4+lEdi;nm%%WPMs(SE_$3V)F-J*{r*6Ne=7Pc_~L{R96I?n%tP0}Yea zN*#Gn?wR!~m^xf!dvh)>GNPD{sfiBBb=i^<(d(~9LOKl3W8`5HG#FX`f+U2aIuV|? z=*H88%5R^Sf%%tp>7Gvj+Z%bzo)oxMUKH!FL?vu-MmPtg>HVu&gSOXqsOI+0$p&z~cM zi0{V5{oUK!K6$F))v|C-snVH8yhD8KLp6N`D6L`ykQPGkb8nv@K`IQ@dH?MrnCps}{CY zS0*kAiDue)Cjiz0wf42psEkY2O|L`1Jf(UbG1YA9GYn(8C{+(ty_-oAX6h`IG~|6T z#~#lAe=fj{Qxzx`y8kiuL+y|A_cePZG@#9r>mTSx)RE1;2d)cQ(UR`|4KocrW#56S zG`7kNjW*bpQSzL7_Ga)$#xAi6lFY$PT|2j9{8FyNZvcZpNBE;Udlb5Btfui9Z%0l8 z89{5(nQTVS3SjAZnh)D8ZGaoKYJ$zrEcgV)Jf^*kg^HW1bAVi+E!7Uzil5xD2ehxR z&sz9zNT?RYa-RQ+0Gg2{o=K8raLHUskjm$uF2+PlVI}k#QdADyOt*O7N)BFnw_O$HCew0yfzCsAIwd-Rl zw}KX67_XUzd}1qKQ>x8nDMV|svo(K*euws~w@3J~u5R@0F~4TN#Y_*RV*X})o>!}J zj?%#suThA8PeG}+li7W{bRwLT(;t4f8Ta_^m23k+I2X-&s1ob5CCB}mF*3&i&&x`#gimax5O%Rx{n6R$^2IL z^?Gg1J~R3*L+$uFL!0x`JboKJS$!8bW7fA3ye$;Ck2yCArm0;4cNwD@cvPQk+rXw& zEkR1gXd9n&7~%f?uRM3qYS7t#3Y?sryr~nN=~!u;n^LvUCj`mt)RL@k$A#i_T}(#} zSJ^T(j4$0zQbZP+R|DUFGak&75Nl_54%fy0^6Z_-)pk^-m4w&rIcrtS=xCG+%zzlc zdL+}h`wqXt%AREQGT3e`QwWP63updW`#WE5@aNgr5#TOocsy4>iDDMsWLk`7au4hC za6B+8xnS<;+fnCR;#=1J2@$Y0X5ynMNZNb1*`i#WU$gIG_`W>SW2#VtyM0cLFq=f! zGAaOm_jCCfft5K||F`lB2-}yb3DILswLEYV44t5rA_<032_*?~h^%~Fzx|r}AwIbs zTjL&O+*nt$d-!XM78Q^lv5-M~AxBLekz$Bt>6SDL22!i%VH~)dF!kCjw|D_-pR&Rf z7`1!ozur07)qg?F?j4;+nTQS}=7J-(vCw`8Hfs^LnqUrNU=^c2ez9<9rRNVemQR$} zd|e`L472JA)_{lNGLTiOliXd?P&N&yxc4AOW%=nbh1Z4I#jIJu-!*^IIm4BKLfhyM$ef zo-R6$>@dCCdLfaxq#8+-^}RO%8v6M8Y`}Sj!r6Xj^fzB(W8`q8vAu;kqthUlN#1XV z*au!UnC9UDZ`nRzI9s}~>P-$pE8X)nvX2&oEyBTZ%*iZZ^m{fN@(dCgDzY|Z;)j@Zq{vpyLKb&q`>E&&*!tOxFl26z1P*ub583< zYHl8)0gexmV|G%DcHEx0WgP`}DlJf$8=G;DRWcS_Xwk0I!vj^)gWnSvmzKL_bl?C zWWh+;CT>?>Tq6)1O8hY$IU`}T72=;IjUbM(lk8d#q`>y>Y>%LRZ7j@Y-H#m6@Mu#BnR zn8rHJ=c|dc6%QY1UTU?NZ{oci=3xvR zT?)+>R(!_1SMBFE+1Jz6CkN1U5LQ~#bUQSSmZ%Bj7DIm*i+;A?)KuhmU$MmC+^C6Q z{8#5m6@;XTkB?^?NwRUhP^^UdSbwYgXVY1BjQ_oR#Vt9***^F!wXmJhDGGiI{aE6) zji$~izJ&a_e$Dz;Jy4o=_QUdv(dtr7uBBtF1UCYKZ53Wdlgt| zV#A;yj#C^9X#+|{osidGp*Cdj8klxPMIhJ@VX)FgS_lk1@RJXThL^2EV9Zh~K z)5nB#1ZmmbCtQMBGNCfm_K>lU8anPe5G_9V(_{y$V>fItI)47)t#UBav}Q9 z&rjzV=7DbPnM08|^gHn@+xbRT^(s+>R79%~YgJk?M`7`FLQ?V)0T`NFxv#FHbLHNT z^T%FGV0u9SfHt^_J^I{ojDvY1G^6em)K9`rG%Bl=!*B>uT>kzfNnmCu=z6>ov}ncp zE=kaO2*t0ttepJKE9FOosAZSkMO!_R8@Of&07pqGKR*|fqB;Eg2r*W$dpX3r$&7jB z8=woNbqeH7$x$(4JqRqAS=*_gFJ}gAc{=q?sKWGv|A_bj=P%mOiHeP`^yM0JM$NgA z<0`RB#9+e?;nTsLd-GxZak39)^gT5qpZs~*-m!myi`Redd-k^JsiM6}B$6zefua)c zQ^JvFt*hv=*6!#u|9u{OW5mfJ!)_@REz&4p*Q zx7ZjVCN7#S9g?FZNRZcyYtNn z`mb1BH2_i#;Fs^1f(2j3S6NCEeyDQ&VfJNGh}*A6jru|SgvB5#WzocJ{4moHn*Ug) zZdXZynZe6zS!j5QOKK$2cYW!%=9zu}}a;HS_x56iMgsa3E@t73r{|b(DS~rneH)Fb(b5Mx~ zM0Ckoc>goq9y-TQGu8#i}~IINfGw*QbP`7p9>n1C2lXurV7QIDXJ zW^)axW}{Xkj_1p}oZ=qyo0mH}1Y8VfFR(VTb_t z?0W9`44$mc3u+Z|~#IT&v{?=u4oWIUzH+}|iGCx`S^V$N(y5>8$?!SnygGOWg| zeaL_5x=qM!U78p}n*8wkr`PUlijl*|MUnqDAS>mf7wy~QQo(?=t<=7``j1Wv?T7oB zdoZjLn&WXL-f9Vy<+U8;m5rRMu}*qimbRs6AT63vRfrm+3C3FP`L>d@#tR$&yRl__ zWNsJoE4;oE$oO~TvCBXo=)V^?hlmK2GIG3eAT#uSH68!+>+;l=PMm(HNq+y?SJeDR zi$A5GDS!Xvgf?;iHJw|MiAkpGa^vNbJ0`y)L*#O-qq>W7T384Xy~=;;qY+{ALHSSV zH|bCW1&yCwSiR9VSsZ1H3_I1P*pxK}R#iUM=+vZfSl;Xicaf064to#z?6uR4Nh!I0 za|@@t27m$Ivgr8-P(0riX7{zs4}PC`9(|_ve=Gl)ZQC0&*?2ZDCz*%b;c*Ay4AZ6t z>^y27IKY#)guQHI_X*?oZQ)DJ9_A}2ea%-(9?cJLdzEqp&ftnTL^$L`C#9H zcx{buFi-J>V0VP>^`L6$qrE;kf2+5Fh2T?#X|JfS>O-l&Nj%(VoE2b&^*b*mokJCA zMk(lRqpYmlztI=7wSK5Kq*CtD9ZuSzTBb8zhbU5|#@l*xV6xAnCc(%WC$ih69G%G1K|eWOIeFpI0GKi^AB zM#hRHM`v%UGALcsydFg#du*S*P8qv**EeS388K#4O^qZZB(wn`YOsE8kpiIUR^Gcb z!8=;tsfBEQ^22Hy8|(Ndo*o*wnDlUO5lZ?=H_0t1Z2+F#z?32H1+Sr^ocRP~wJ$Sh zZV(V^sJOd2QeCWqPgw9YxW_C5bEkDDSgz^BMX%Er17&s!JM9<+pIC>9l9Yn!voa`mHSy%%bLoyfAOq8l5$((dSWmIKtA^hF{`v&O|D=sDT%v2v z*Tgb7yRq#tCa&Z zI5rm1m$7A5U{#=h^Mxm+4@I>L)D2QYMQ`3xVaI1*D4xC4%Mw9|?v>xfe@8j1OT2Rx zpCqU51q;3!4vqxY>P)+3O-k4%@ZzDglK4gnMEdDuf%>?G|G>fF+nV@y%fHZ%C_pRI z+`2W9|7vXwRi|fY%%<=7{++HzoOIKSUjmaLhW+XyE*Lo$j|V6hy5S;fHRkNI>A>))ChAw$lzdH0ANiSrx_-nN@q)b+1 zKjVqB@J7I}4SPtBo5dQ-c$`dhXE$SQO6t&{n)KtqrOhO&h_qWtgVoC9#J4yKnYWTj z|7NGX<EwhP$oR|Dv2Ya*_UTKAtILX6A0zIMNehgI3I33q{?Zv*9^~jBEW)y zjFU?^z1R_WF`S^93-vNENDay^DI-*$_5>Jp}uKKD8f+_j;~Y%jV#G@#@7GIaJf zBRoL@JAl^P7%*{ZLe_2U-OQMgeCMBnvQTJe)0bz_POuNWQB)koK$QJ<7+Z(9j49iV zSr=-26qByzI+AbZ2K=%(DqZ2&r!85$Mrqsc`4hco{m`*`*4Jl7C_GHcY6GSBgS94D zOWOk5nKTR`bN@x47grFs32A$0EfzM?Z5rGM{v&kHI_@ibZ% z9c=M@hejByUhH1xC=_zmbDh5Yuq)V|SZ(|WAgt#OkBI0s+JXg6)5pifP2nJkIyZg} z*%D-5kTQ-`wT^ehjGNdHHn|_%ElCGpa!a4_;^5)wMkIZMky~g*4VB%@U=B4qr;cxM z)`72+hYd&p#1}H0Evfy+j|>#euX2_4Gh{>7{3?$h;@@{(5gYn=h{G5c$1zuM{=85(b8e-OP3P}0#xhzBJ(CIImmt?w8hebe zXau&l8UXYRVMeuXu6q#z*OS4mi%uwDoPAQr99U004f?4H_69kPHk zdv`K7hw@qw-&{y@bFISQAv;>h99oHr{qvxuKz-loWjbQwj3>*QCfntfou5B{Vx@VW zzPYm%D)it606m7`qI)p&Rnp<-0q$RL!PCZ`K~A(;&$9JPbe5+xwc?GCfu!W*M6!Su zKV;gEgak^bjlaar_0mr~ii!UW&O+d(W~!u}3pVM(47^$*i7;{@^IcSjN^T0w;cFuV zwQ<6X@@#EA56$JbDw%1MKih*}o5;+P-|!L`_xGX@3@)AUT0L{94A2ia&7hHt=8Jq3uof zRvzL_wf@>f!Aop{1Kzz9DXnL`TeP}~PyhL7Ay|uO*lo@GKoV4GA`1e5)|)GQ6u0;> z(VSNl*tzgZ%k`z}7|UozMpsiMJW9VuMCe8iyb6-66qee<+Sh!ddMvEK(;M15P6X%; zMYK%T8=qmVq=W@mT9M{zr%OUQ&%Ka2bNMl9|Yl+?v+1@=2ig@W1N z-H^ht(&NxOIy&YZvZ(Vqd00n`IxIj;pznl};sSgy zmcEFUC1FeD!+X5~wENHtlRYk1MTl zVF}tOsz{e_;Uz)k^upvkdL92xJ+0C@9Ivz!T09sb!EI}U!)(?ex)h04@e;e~;&G;Y zVRd;T8)o*ENgP8&#nN{RMM|^!EHu-HqBndl`P1IeJ-;5UZ!V}^+BXo7uMEr}H$~W| zu8a15*~e&*fdQXa)y2k?&E%1#<6eR|P;)R&ER8qK&L~PyItJd}xre{agodKwraF%da`K->CG0 z=Q~u2(cu{h$pA4APRNvHs3F6#-askHTvEe*iOkoL`?SezOS<1}&m(K)$mgliyJvRy zc(qf)&c0`T^G)04TtvBme?~3;LU|W=pb;x5u`%l=OlxJG7oUJ&sC)9$2*D9wlVp*I z(r4@C7KvplgH}TU3T@YC9}Rh~E3!#+{LALyf0`aoY5aqI@i$^+(OQeNcg2d?GgNnO1+Ir3{2t)X8gYU%R@nLva*qdp~{?5)$ts1L22Uu>b zmf?E?qJh?ZoE2DM%p(h%-MR}oEh^PgvxUp7fQ67CG1KElDj-Tkk`4QJ2yjHK6 z3a2ee4RP8;60{>8aXH>aXd2Rf*|O4V9{$qvZ+6!h0STy|YJQ1gk?{q6YMv^d#P4Eu@6447r$FO znu#br_l$-%?#hxzaDLPQtScFD{`duKl0bRSsvh~Q9{C5;|1IB6*PiD47|5&9&rp8M zRBUl>Ug`X;7u8ce;T&d`AbERQ2q-tYIMR@B`6ymu&@4*vZkY&C=eVWP-0&IDC}l}l z%zg`iUVRdTfoePvOTjPnd64nQ-k}6JU5TY~m(`A-7sU^j3GJeFlC4!XekH@k&d0_( zyM`*CF2vU)Nr6yy)9EKH0yv)WsuP*-d?*|hB2KvzUJi#S0n`Pr zpa{S^GoQgs0Rz@hP5-h3`MMGfX=_mRpXMYKT%PQ#Y9EW|sR#|lCdqZ*p7@7q42?cE zQF!xRdW+BdFf>ZBU}>9rVhWh$eGj6VFm}fQZpKXt4Z$o%(>GAXhD)A8Q_Y+=HQQmz zHhsm}xu>C+-ZSynd+m7V3yqmQpj~Ou)Ue~Y3B=qO5?k|Lf=3Ol{W{)bRD*6nU` z!$>Y}?t@BH^&!@C?VF+RJSet$1QV;STk-%2GOMKENah$tn9<0Vi6xdy!Zb=N-<*Be z8RVrN^7#ejHxwwL)}yc9#0O{?ioOaIGXM119ieBxS+t0j(tcK$Xm3#Qk2;^_CqhW;w%^zhw!?z6tZA@!7!R*M9_m3 z@_{LsepTa~Y_wO4FQs}uMn=EvE0_s>+4qjjFEcpFujgHL>Rw+z17Z(u0MMMr zg+*inK4s10bTewx2O<^DE~8{|0ftv*|Bzf}$u`_1MUXhi*i03`7< zP>)`AR6*BNrs|`SLbD=UT~wJ-``C*F*pO`SX*#h;c)~uab2|PIZW?ImmSUk{zc?fQD05 zSW{a=e&TJy`+14jO%fEC@&&^`n0|cIj3~Mdk9AJ}Rapvt&;79x0Dk@-pCP$7Q>NNw zEpkygBAMhLVLy6z)VjC`whIKE#HkHs zha#ABa&zUk{*D(0vTE}p#}(gL(S(XBU|fBkX-S>QhV;qu4~o~?PjXTvw6z4^-_a{6 zDUn#As7*%|tiC6V?(U2#+_&@tlg9F+(Rt`l8#lo8{jvfu{**=y+6OJ}e&rwZ=>%djT?dS0-#0GK&q`62i42+cLFqdp5sr4vRb9+#stZb$Tp%k;L9N zwiG60+GN=jzscZAI&=d$9DF91OQc+%to}^Smf?$8(8g}}2&u}txALL}s(1aAA!_C@ zq>72d0M+|csTGX1=6LhX6KCh8{T<-H7Ih<${GB&%6Fx8APU-82Ne+|pFLg@rZ*U6w z^QNl1xMqduuaO<2|0XiZa4J*Z79QE?b`bMzHi|e21W2>U>K~GkoPwFEw zuoSL}?+Rn&7B9mM&2^*n4#Cm*QvsZotQ$;(z0bnt)kvzAAyKTr+z|`JWi{V4x?3j! z2OC5LSHiblBbMQ$@)|LDbg1C19pc^x_K5~e-k+V>c}sM)$v*r6+6m8HR#C^=-357* zyXe>e_k}(-z^g8f0fe6NLo$-gDWFMfv8ZyD+7o}S!OaSM=|adD1>`TSg`3;8R$**m zfjrJEkTst{lV8Rz0ZlowIW2Wt3;1vP0Tud$D8&z< ziSNnSFeI&&w8vppc3YTdo$FV&Y7PO3YJrJXif++4jJMbD% z4fj-$Nr;xD7}KntE6v#{#AjmIGhSB~OoB};{arzu-w;fE9omKvwaLoM2Qg8*mG`Yg z=G_|wd&O3qShlsN?BC?y->%#fzpb&+!sgUw#x^IQj{di(r&9pLoWlMfT9l);Pu6U2w=@6$=+SA&)z1zB@zk<^;qM9 zL@RT7?k99+9G~lh>K#+QxjofEnk4hSn3L(6SK?oL!eP9~idN*GsX8|2GGsd&Ui zLz-NvFzD4wA8mU4k75@ch9UJxbGXg@>Rx zD`Z4U|7~EJqH$Ln4Kp2FiS1Bd)?W+sCL3W-8z*49SZss6LSJ|`rS?qvk{Nu@ih zrZ46Xu8M%D23a9`^P$Ix`)>f2enfz+g&f=-bPD#krfy-&o@p2YL+b z$4$%Cj`KfzPmY&9c3C{WXuIqBDl6M0^C^dP6c^s<-n@)#S^bHtRc5|v8PHR({dRL8 z<%;``#Zx)B{`CLY0YtcHusl&P0~hF{MUzMb=NeGWK{NKYG+cH-?(MUIWoK{s3xDzA zzHdOzk#7eG$KjqZEV=k4*{qHRUDVjX6?4XQCvN;a79paqpDc)z{jt}XvbN&p*Ts!_ zBqU0Gz>hN|VSKdPy=B%-V?S5pUoP7A&8*6t?*4&@w!tIropEV%TCBU{O7?N}!P_pq z{&7tqY1qF$Nlw?%i{T7Yw{m2Sl*Qp+du5K_TKczNzZL4mi2j!S;S1q>n)Sw>*D=l% zFZRIv@^kuECp`BRpQ}<4>_oXaAR?Nd-9IoP zEW@PQJOtzWS>pp5$6Zz3q-~MmJL7;%nl2QDaF-lXi0N`;5D#XEpo895C^}Oh*}#w# zdIRQg%!Gq@y>^lLisyXQ&mNzqLnx zvMr20gm~#%9T|w$gW~RlcaT1qm#eepw*b8S0{G$(+qe-ybnExrR!Y_N@|Jki5?QLz zBkK6_wG=PGdp+2tgCXdRIfq2?RsE9tpbA{fw=bEM8{C1r-xHXc19IzOfi9TlvMEzu zS-w!M>3jT%v#+D&t^cH!1RszW-A4nK!>-}$$Od^V)^{{SEBOARk2?Ej1r&m*(VxmE z(e5$rZ{7!A=`lTdg=1}R{3%QeS3Ur={JS%AZ>I{p2DDNJq z8mQ#9(Q?qt=I2W4*w0Uqn)^*E?}AZP&6TEI6t1ThIySry(=xcE;38q{Y3Gk_Oe2*~ zi25kuVRhy$EUly0*3^QoW`Cg=`fLdz)J_sA8pty=Ro4?oNJgl*B|?m?mNMaVT0! zKgOklNLt)+PLLuKfE>kCQKV}jnGL|H`h+3kdQwr+3deFF#H`*vkToY@@--U~&gUfL zHz;oo<_W#jUw(6W?)}#p@gDIS{PfBPmHKwwndg`xt*f>ljEoj;fxd`FX6hp!%{;r~ z70d*IUPYG6|0MeUDw!%rE8%f@hq_RoYsIQVvV|i%r95k-!T+r>Won#ID&8yaH{esU;+`iiDVW z88?gsbj4Ca8Icppy%{dRl9RBLDsDef)B}8V@2U_8;fvOwp$P^hFQj-vk1|;_M9tr; znAzV8-z!0v4R@iZ>n1f$P1t`!d>Vc|=REsUpU*noD0sC!)G?u>B{0k_B zp$>hL6wGi7urTbn#=%7Ia=2HIIRmaz=7`bNgb~VEzrgKQ+l0|$Dh(pKCi!KAC6+;W z+XIGyc-hiZliF1hR+MB4xce&H&VLtb_sCN>sDZ&CJLy8lLfe1tfG}S*V*!nc*&+oX zT}6hPdGf!A4)R9B{;{m_zPa(YTmJXvY}@YA6VWY{{5lNArHGcW+8*utJzH5F3yUsC z{3O&bk?Nz5hiY{%isUNqf);AO1hVWM+wlsuDHH8xL6T?mqsV`-IsVp^RYyr0Keh^X zW($nqGsHe6Mv1&-4qD=3^7USTCXMfZ_O1?>$5~3)5n*8c3wBmA-(W%bg1pXX!iAc* z)Qmzg8tMWfKX*_&Y1L4O$(FZb@mQ(4VLanSlplrSeO5%#j_r#fv!cldBA!It3m-BD zXk%3oge3oI2h1RibOEy)R>65wK`zK}%Vl7c^h$vvv5WDUm4H_fqnUG;;x9SFJL)NQL zyi#PS47cqN!G^A-aZ0+(Z`PNJzQ%o`B7o*&l9p=vuFga#;b`ZcVsNLtbj>AY2$`?{ zSjUxH1b7qtEK^6^;`ssYxxQPpIsXjImMU!m#ph9_pm<#mz~;>b)fQ46Htd zfGAf*N+p=ozSoUd0h^CG^wGG!Q}GoUOU-k| zI#mjLCpL(eH98xKTw%|SLU!e2&EtQ` zc*oU^<1KcTN|XeG(g@wra%CsAA8h_e(_R6DQi&gN44|vJX4k#>Kf@9$@iv4^0OQO| zeE+ZQo)I;sOzMT+6bPOt4@KSF3m{uQD0~-#di12PoEop7U)#)vg}u!Zz#MZV&V*3r1o-z@D#t1k8QnPM+}>n}VLKnHyl+@IVL;>1;MD z-e`oVn}Xw=Ixqoh0h)fVc^ztojp2M)2IFVKnpZuGt~`;85y79o<3?8*-aj_^4}r(P zOT-#3ayV^xl;N=)>)HHvcPvE;Ad~n9zj!f_vM;!jf8Ycq5_fmtUp~Vko&MTCeYK`Z z*-b**v5yW9myw!M$jHtPP;htwz21Y>lli!H!TBlircEX$m_Pzdd{s)jng3M0sPJrG zYp*cPmi*C{SNa-$Xb*F_I-Bh}%X3Bg<0ah72bTO;JSxfKwb`}P+79F80cAom*NFOf zv@|uJcI&6=7)gYozv^K#}d=;S^9xeAEw*OW;;2wOSsVmev-i0365sB-9>8^} zn88XefQ0bT<7C4$mV9-3ET{Xc(O~2aKqWHP0@EAzF^5xCTt&h6ue=`%X!q*kzPGnS zC($Ii5xi?Upm3gJ!6q6L+5vB3=kkKc+Oi)AV%{7zs!fhOw3Fw$Nslb%4)+#-3FP$?3G~UTX)2Re}`lDRPNsn>l359c z7c%L8{tw*w-5w(sw{8)Ko^d-fJ$#b>0NX+}D6Fx!Y3>KbnT3!}34 z;$ShlkgvCM4$FegH}#C!JdCEC)<)0&)blv8hv;LMYEBF_@1X!d5eY_o;k77#C=L?~ zinpRTKBe9xhEDEHR1t0s>%!%Ox}u0Wv{4f7%HgxYjNrU>*$=`rlG!W%n9?Cd`G&ed= z4QIb%#|Yfvlzww*wS5q4FZ1X2-%65|7DY%mK*Rd)BNOQHety~1e@6ZPRi3NMQaWjVn5~VlJ$fte;TYjnNllWO;ww=7q zGUGf9x|ekUPmTo>#R9Mlx4hdY5y13LWFrv=(nCK_qbGK(Yd!9w8xkp$Jb0>YOaTPf zES$fNug<(Kpo}~Habmg~eO?|YM4i?=e(5)~$wzZ92AdOT*Wi>%VzNuAqs}H2z9Z2Jweov>nbS}}0TlnOJy0^%=A{=R#)oCzns z2bREEF-sK%#lAe?9X6xKV1uZYM_f+4Xqq#MT;#)%5yev4Y(1OM@XJ+zTD2pDF1uAb zMAye|=n9FQtQYB(p_F^?tO))#<8M~5sMvyKAt*c1bTx|Y(L9AujI6n+?tF#Y9i0QA zxK0F#gGrKE{E^CJ^jNU5?0c;3k(?~B=AQuJPpG)xpM|hxy&^5ntb(uCNPPr{4>hqi zrqw1(sHCo5I4pmkJbha~ZOC-{Vm!O!W=bQ3H2?hEs)(Z@mHNw%M}_ZSx^ca~0Y|*RscDtpnAyoC2VOr(?k*fxC38tqRd*3|6{+);Uq94szP)$-9V*l&-*G71)ea!+$G(ggtP5)W6&^A?hiIXlrljT-Ple2;LP6KIskV z@}JXFRlL54CqvRGey<{H_R{#fk|maiQKHX9ua)(B_3Y=dZ>#T>E@&h-434uE8xjLJ z6|(Z{Z#@+t1coMC8+=oZ^npEG5Qh z*+_z~Hvq*_7K4*<9C!d%W{aHJU{idrF$`Q|KoWcz)pfM^MQD#L?!|Vi6Y54z>w#@l zLuynb=kr0u)Su{?hn(5yln?`tfn0^USK1><-+>YxZcv5*@UCbO^DfYwf$(GYdH`9_ z>a!7nEYX%s`cIV6GTnflWK2TnUu(?Bz%KS3-|m$}7t?fBy|+RUe@X}ZA@ z6U1gz6L}FmRya@6Tl3~NUhn?1nnLRj-&i$FKJ@}n5&b9D+~hoJCZ<;WVjXJ@?Ssh@ z9nQ?ocSey&GUa*|V`RRs3dTB%i2^z%pEP0Z$}M_Q;aPQSS>g8@C^mUDb~jcTGf@}f zJ^#2}ISsbO&_b=llhI7Ey+h9=<1hfvez;ENC+dkZ{U=faQ!3ND#PQ?174>m{!PvNJg9XddSe66WqQ*~|K(`^w?+kc5 zUy=3rVZT**pS)dk{=S3>Asu7$A{THmZb%rViwYVqI-n&L{|$2RE#qzwv%5UqO4}bE z@$n8D%Y7Xw+${83iH;Jp5u47;if1LZ+}R8A>08Kt2n#Gt{ClASQ(+d(kwt_Pos14c z*!Vd>43Y*u#3B2NOBCwSRDdwMhUM{tJO1~lAeBO*!}qEI491r*%5LcC7xPRNI~Dv| zp`cg(R!nH28!jCb3~HYyq-b1n3N+4`pq`{wRH=?@v@2o|SgQio@PBy3bZ4wv8+#2B z+Y|B>lP8jhlnFhQ2Bsa)S5_69ztH+E&dmQ@_RRU~_IM0vuh11S^qD)3V{p@RORE0o zqoXC5AWW>}zcX`xl#Aw%&Gx1_gq}8Vs5vW@Cb24lOIryDp5lL^%1c}FDVqTW+?x&(8-~T0-D4QDY`}&Gb4FNUAE|K}z(r#;y244r0 z=H^_7^uzVtF+D@2jIauZlmkWh>hUCiyGvhn_3-=6?&df4d@LNB5%n=W8Y(!;la65~ zXBYkP3+C`D9sKk5RisP6I{kGpa13Jz#uuR5GNDZncng9Q5x}T!QOAWt70F&Z`!-?f zZFP?-u=;;UdQ8(6K{`h9yd$|%$Wx!?z!=Z{S&uV7YLf-iN@Fpm0Ly8~>uVKNcqP*T zN0X@}G!oFVbb$9F49147gskz#wEL}k6HUREv!C*YiAxKw)SFD~zP})GymHKGZuZ>y znSrLSw)a-Pq<{)(avVi%|EFR#R+Ugf!t$T}tKGMy2rM3NsPD%(SGea-({RWH0G2uL ztwqcB^HHr zhoWBQZt=Np9j3}y6W|O~5!f3Q)aG%1p(fc^jxG9ku;_gkflu+9UhKnE$trhR?Y=Rf zuHlf@@1iV0iskLHa-ksyR{W|NNA}R9(k$Iiw((|EJXP4k!89D7vG`9XCMUhNj>q;a zku7lPR&tO;w{xu4Jn4Z=74L@mBy(qNlv^g~S=-qK7$Z}XR;?cR2TU!a<#mvsIw^w0H#-nzVjN{O^4T)VrnxJP`M zA4g>{wq|Kd*{v%;2agvHIdpd?kp%)Gg(KxpaUXXg0vFd)gdY^pg@aKngK5< z%i`wws+)KNoROBc%y@~Sk5@%X3!v6^Xelfk5!_5!@J4+e?ot`56lES%32{+grH1*NK$-I2qQ6jeAJlR zTwP(z;MT0jjuEBkH&HeUp|Ef+Lb11^X^x8aAvN^+h$*osYvv`xmTgK>6aGh$REhwMQ(jPh1aArZop`vdB z4JRSqmO3;mM_;yMI}=oib@>MewRLmUqp^w?T=PLm;awOeF($1ZD%wR6y9N`iPu zjgtE=K=+i=^6!dVn5F(Z_O#gM-%4zkC3`bU+7&^K-Vmu=ntt9Lmp{!t;#>udJqIerJss2;L?RHGe<{1CRy|W#n_Sj&By{hI;zB^042L<;*k%%{;Ij7 zv_C}*7a|;I;BL3URx~PzN3*#ec)g|Ld(GPIp>HeT17}pOFsBV0mD;tkzlC;8ImB{LY2g`b z=3oehzvC%49sAvy-vk7yiiu-qAy3f|ru~;19+Q%;wZpymuX-^y?fKeik$ho0HE;J` zP;DR;33WMwExN79Ctl&`;}>{D(QhPJ<>%$2dUXMvgU}-Jmz#D-Sa~hz=+7x z@{ki~S1FuYMtxrgjS&Bs5K0BRWy3uo$w{Cs_K@be$o~{C zRZrQz6oWtnf7F3J0!wxyTQ(xGcf#f|4Cui^0l7pcop@pV;c>XJ8=k1y?hb4Ygj*wR z`C?ixW0#9R-^|zLOc}*#x4<=`WO}^q-O*e~*k+Eb3z{N{5))B90Gzx)mgl*CbQ<}7 zwTf3EBN~?3vd3-AI6q@eJ^BGTXM1(xMk4y?aRgsn+j?wlY~4HCr!G;zV@<%ecQKYb zvpaGjyBVnOOS!zf^j%teYU51k0+7^FW4m$@?wqKh_HH>CU{&nA>*Sq01-_TzO}jkH zqDIJ=!^^py7iY@$%il8vm@s1%iCBcszNcQKcke|{`Tgg?1oIcZoh#RXj&a=j7%dQU z-T#Rbcp(WLltHA)rJpT5IBIi0~eYdL3gC2ck?b#qHO+2NP zUQMlLQB5xWLy}Lr{6RWjc8@|)0@twgWGV@*d%c?uVbI&N_t%l!WwfQY9+zR6!i4X7!@_EQS{F69-278 zPp_?*tcCKFxbz6lhjK51DHShDf^X?%!JB2l zp?|)WHz=j?QkKRefY$R>hVHVdX$-gZMt4RKwr#ohs?c&aK%;tQ8;$~sz<8N zpF6P!5@;ERzRmB2Tc7^bGnTMvx8FY<{)sowlQK?DE7(g$mKe0FMQ`_{BUO4>JS#f3 zHeD#aFx18(Jt*dYJ82yE)LrO_wunwv9*wpxDoIk{?XG`iy9a znrJU$|MRbYyLd@4o7@%Rf_1g}jG+TUwngWIWBHA9m-H6jq`t42BJMO(``jn|+pXnu zDSzoq7kW-@r(w^$V?}8U0Ab*WF9vSokw5(kQL`1}dzkHEAJ5jk9S;#eMtQUcC5X4^ z&$_t8^Vl>~98W1P0$;levaM>(=h!dZOw@J-!0H0234pm^)3RBGbCyNN$Y-! zdz8X`$`#P>%^!F%Z>x|eY&~EL?s2c%B7!>$oNsjUT2`JG4CcI1Mv1s|)hLCMBZO>T z0afTY9|^y0*92t*=E}cHs*b;+qDGasLY=rN_`8Qr_wAgoAv0R5QaHwF6d*`jkFY1n zvZ0=!76H*(2>va#?nB{LP-$n#EDm%Xe4^%!V|cd-*@18(2}BPxWu@-%I?e{V&*AFQ z5xHy%SWf3S#eFYfNm`Dvs^ccaX7}x|4zhnx?hhV^=n9+ zn&MF%N{1wm*jsqbr-&lh=~V{9Sv4#S%IyZdjo3F(j??zev;3D9t7tius7GHUMPp-#Askzh)hMlD6 zJFWB}gYp9vN!2iWv@m1~! zovoc=%<%>$dAAr>+y>C*`NZwB(Z`85J-?7JSV-Z1sUOq8=%v!?^G+%X(3T;-zMVuV z)GEzkP^FwPvRa$t@nfImE!&&8+c^d=l22xt9{m_J@{ztl@vE(&duJ3|ImpB0a87|< z@pM%rT194Y9<(O7^(e&{!|Y>R|0_NIH95h405|S02i4K8eWkgH zIS&03wVhgajoje7tJM3uqx;{NdG}X&*YIBT1%Gta?<$2LH=2eI*sFhv{k^8hCoNLa zzvLuY6}p72ZJU2=i9T-a*DkK-aO^RWQj#K}_wVk5Q|cpYKs`8T*3DQregr?maGS7Y z6IGwYbvcm-whn8f_?z2pr{>k*(FaG~_F?hpOJD;Bd37IcF+*1K<+P!h+yLL6_Mg3@ zhV{fv04|IJQq_>UyL6j_bvTGqmYgh83ha;#WBn5(WdVy4qltT z)}*W&9Tm2QWw9#$c#t7(|NQ{calhc@s1BQmzQVc)0h74*Jn zKkjc3QLrqzd~$Icy-{VIe%vx}a%#JNw6^!KqNu8CgyZvCPFcILK3mdwNnTK9ocK!5 znk1v*Sg>kFLdCrc2GKot@dz79bNM(L@M?z#3MziVB6N!n`RyS8S(Xf-%-vyqXhIJw{*~Q)wcxKySHd z07tZykUwpGU;23^qkvAmqURBC!IZa3y7U3EIQOp84}aN*zGGE-riW!t$99KiZem1J zg&>2yY36gQRl0{|PnBd!S0y`)4-1`Se9r&*C71(ynr6WTH15BrrEThRW$R35H` zW=U;Ja(D!MeuItrcAih*!*8C4J`ZM|efqHeX!ySog%fo9!$KUaxI6jEw-0+{GC7u~ wg-#p-AKfbeJG1WZc?&@;zz?_&(=HZ>1O#;^8es { + // ── list_docs ────────────────────────────────────────────────────────────── + server.registerTool( + 'list_docs', + { + title: 'List Documentation Pages', + description: + 'List all Simplaix Gateway documentation pages with their titles, URLs, and descriptions. Use this to discover what documentation is available before fetching specific pages.', + inputSchema: {}, + }, + async () => { + const pages = source.getPages().map((page) => ({ + title: page.data.title, + description: page.data.description ?? '', + url: page.url, + slug: page.slugs.join('/'), + })); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(pages, null, 2), + }, + ], + }; + }, + ); + + // ── get_page ─────────────────────────────────────────────────────────────── + server.registerTool( + 'get_page', + { + title: 'Get Documentation Page', + description: + 'Fetch the full markdown content of a documentation page by its slug. Use list_docs first to find the slug.', + inputSchema: { + slug: z + .string() + .describe( + 'Page slug, e.g. "getting-started/quick-start" or "guides/app-builder". Use list_docs to find available slugs.', + ), + }, + }, + async ({ slug }) => { + const slugParts = slug.split('/').filter(Boolean); + const page = source.getPage(slugParts); + + if (!page) { + return { + content: [ + { + type: 'text', + text: `Page not found: "${slug}". Use list_docs to see available pages.`, + }, + ], + isError: true, + }; + } + + const text = await getLLMText(page); + + return { + content: [{ type: 'text', text }], + }; + }, + ); + + // ── search_docs ──────────────────────────────────────────────────────────── + server.registerTool( + 'search_docs', + { + title: 'Search Documentation', + description: + 'Search documentation pages by keyword. Searches page titles and descriptions. Returns matching pages with their slugs so you can fetch full content with get_page.', + inputSchema: { + query: z.string().describe('Search keywords, e.g. "authentication", "credential vault"'), + }, + }, + async ({ query }) => { + const q = query.toLowerCase(); + const matches = source + .getPages() + .filter( + (page) => + page.data.title.toLowerCase().includes(q) || + (page.data.description ?? '').toLowerCase().includes(q), + ) + .map((page) => ({ + title: page.data.title, + description: page.data.description ?? '', + url: page.url, + slug: page.slugs.join('/'), + })); + + if (matches.length === 0) { + return { + content: [{ type: 'text', text: `No pages found matching "${query}".` }], + }; + } + + return { + content: [{ type: 'text', text: JSON.stringify(matches, null, 2) }], + }; + }, + ); + }, + {}, + { + basePath: '/api', + maxDuration: 60, + }, +); + +export { handler as GET, handler as POST }; diff --git a/docs/src/app/favicon.ico b/docs/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..190a85d60b08d68d9d0078118d7a34485bae64c3 GIT binary patch literal 15406 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 + Simplaix + Simplaix Gateway + + ), }, links: [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a19c06..fe5a8f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: docs: dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.25.2 + version: 1.25.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(zod@4.3.6) fumadocs-core: specifier: 16.5.1 version: 16.5.1(@types/react@19.2.13)(lucide-react@0.563.0(react@19.2.4))(next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) @@ -84,6 +87,9 @@ importers: lucide-react: specifier: ^0.563.0 version: 0.563.0(react@19.2.4) + mcp-handler: + specifier: ^1.0.7 + version: 1.0.7(@modelcontextprotocol/sdk@1.25.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(zod@4.3.6))(next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) mermaid: specifier: ^11.12.3 version: 11.12.3 @@ -105,6 +111,9 @@ importers: tailwind-merge: specifier: ^3.4.0 version: 3.4.0 + zod: + specifier: ^4.3.6 + version: 4.3.6 devDependencies: '@tailwindcss/postcss': specifier: ^4.1.18 @@ -1409,6 +1418,16 @@ packages: '@mermaid-js/parser@1.0.0': resolution: {integrity: sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==} + '@modelcontextprotocol/sdk@1.25.2': + resolution: {integrity: sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@modelcontextprotocol/sdk@1.26.0': resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} engines: {node: '>=18'} @@ -2307,6 +2326,35 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@redis/bloom@1.2.0': + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/client@1.6.1': + resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==} + engines: {node: '>=14'} + + '@redis/graph@1.1.1': + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/json@1.0.7': + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/search@1.2.0': + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/time-series@1.1.0': + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} + peerDependencies: + '@redis/client': ^1.0.0 + '@repeaterjs/repeater@3.0.6': resolution: {integrity: sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==} @@ -3066,6 +3114,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -3129,6 +3181,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + cmdk@1.1.1: resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} peerDependencies: @@ -3158,6 +3214,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -3910,6 +3970,12 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + express-rate-limit@8.2.1: resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} engines: {node: '>= 16'} @@ -4151,6 +4217,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -4865,6 +4935,16 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mcp-handler@1.0.7: + resolution: {integrity: sha512-w2wHb6IVmbiS+pnBNb5BXaSd+ynSgExNauB55gUwoHDw8Q8Ew9TVMsSX89yItmex61zQTl+/NuSYmlOgSpj8SQ==} + hasBin: true + peerDependencies: + '@modelcontextprotocol/sdk': 1.25.2 + next: '>=13.0.0' + peerDependenciesMeta: + next: + optional: true + mdast-util-definitions@5.1.2: resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} @@ -5766,6 +5846,9 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + redis@4.7.1: + resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -6580,6 +6663,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -7755,6 +7841,30 @@ snapshots: dependencies: langium: 4.2.1 + '@modelcontextprotocol/sdk@1.25.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.9(hono@4.11.7) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - hono + - supports-color + '@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.7) @@ -8676,6 +8786,32 @@ snapshots: dependencies: react: 19.2.4 + '@redis/bloom@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/client@1.6.1': + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + + '@redis/graph@1.1.1(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/json@1.0.7(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/search@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/time-series@1.1.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + '@repeaterjs/repeater@3.0.6': {} '@rtsao/scc@1.1.0': {} @@ -9523,6 +9659,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + character-entities-html4@2.1.0: {} character-entities-legacy@1.1.4: {} @@ -9595,6 +9733,8 @@ snapshots: clsx@2.1.1: {} + cluster-key-slot@1.1.2: {} + cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.13)(react@19.2.4) @@ -9625,6 +9765,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@11.1.0: {} + commander@14.0.3: {} commander@7.2.0: {} @@ -10510,6 +10652,10 @@ snapshots: expand-template@2.0.3: {} + express-rate-limit@7.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + express-rate-limit@8.2.1(express@5.2.1): dependencies: express: 5.2.1 @@ -10813,6 +10959,8 @@ snapshots: generator-function@2.0.1: {} + generic-pool@3.9.0: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -11560,6 +11708,15 @@ snapshots: math-intrinsics@1.1.0: {} + mcp-handler@1.0.7(@modelcontextprotocol/sdk@1.25.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(zod@4.3.6))(next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): + dependencies: + '@modelcontextprotocol/sdk': 1.25.2(@cfworker/json-schema@4.1.1)(hono@4.11.7)(zod@4.3.6) + chalk: 5.6.2 + commander: 11.1.0 + redis: 4.7.1 + optionalDependencies: + next: 16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + mdast-util-definitions@5.1.2: dependencies: '@types/mdast': 3.0.15 @@ -12995,6 +13152,15 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + redis@4.7.1: + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.6.1) + '@redis/client': 1.6.1 + '@redis/graph': 1.1.1(@redis/client@1.6.1) + '@redis/json': 1.0.7(@redis/client@1.6.1) + '@redis/search': 1.2.0(@redis/client@1.6.1) + '@redis/time-series': 1.1.0(@redis/client@1.6.1) + reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.10: @@ -14096,6 +14262,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yargs-parser@21.1.1: {} yargs@17.7.2: @@ -14114,6 +14282,10 @@ snapshots: dependencies: zod: 3.25.76 + zod-to-json-schema@3.25.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + zod-validation-error@4.0.2(zod@3.25.76): dependencies: zod: 3.25.76