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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,23 @@ wrangler queues create github-events

```bash
# Set ChittyOS service tokens
wrangler secret put CHITTY_ID_TOKEN
wrangler secret put CHITTY_AUTH_TOKEN
wrangler secret put CHITTY_CASES_TOKEN
wrangler secret put CHITTY_FINANCE_TOKEN
wrangler secret put CHITTY_EVIDENCE_TOKEN
wrangler secret put CHITTY_SYNC_TOKEN
wrangler secret put CHITTY_CHRONICLE_TOKEN
wrangler secret put CHITTY_CONTEXTUAL_TOKEN
wrangler secret put CHITTY_REGISTRY_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_ID_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_AUTH_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_CASES_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_FINANCE_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_EVIDENCE_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_SYNC_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_CHRONICLE_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_CONTEXTUAL_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_REGISTRY_TOKEN

# MCP and package auth tokens (VM/runtime)
wrangler secret put CHITTYAUTH_ISSUED_CH1TTY_SMART_MCP_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_CHITTYMCP_TOKEN
wrangler secret put CHITTYAUTH_ISSUED_NPM_TOKEN

# VM runtime injection (non-interactive auth handoff)
bash scripts/inject-runtime-auth.sh

# Set third-party API keys
wrangler secret put NOTION_TOKEN
Expand Down
52 changes: 52 additions & 0 deletions scripts/inject-runtime-auth.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail

# Runtime auth injector for VM sessions.
# Canonical policy: CHITTYAUTH_ISSUED_* names are authoritative.

pick_value() {
local current="$1"
shift
if [[ -n "${current:-}" ]]; then
printf "%s" "$current"
return
fi
for candidate in "$@"; do
if [[ -n "${candidate:-}" ]]; then
printf "%s" "$candidate"
return
fi
done
}

CH1TTY_SMART="$(pick_value "${CHITTYAUTH_ISSUED_CH1TTY_SMART_MCP_TOKEN:-}" "${CH1TTY_SMART_MCP_TOKEN:-}")"
CHITTYMCP="$(pick_value "${CHITTYAUTH_ISSUED_CHITTYMCP_TOKEN:-}" "${CHITTYMCP_TOKEN:-}")"
NPM_ISSUED="$(pick_value "${CHITTYAUTH_ISSUED_NPM_TOKEN:-}" "${NPM_TOKEN:-}" "${NODE_AUTH_TOKEN:-}")"

if [[ -n "${CH1TTY_SMART:-}" ]]; then
export CHITTYAUTH_ISSUED_CH1TTY_SMART_MCP_TOKEN="$CH1TTY_SMART"
fi

if [[ -n "${CHITTYMCP:-}" ]]; then
export CHITTYAUTH_ISSUED_CHITTYMCP_TOKEN="$CHITTYMCP"
fi

if [[ -n "${NPM_ISSUED:-}" ]]; then
export CHITTYAUTH_ISSUED_NPM_TOKEN="$NPM_ISSUED"
export NPM_TOKEN="$NPM_ISSUED"
export NODE_AUTH_TOKEN="$NPM_ISSUED"

# Non-interactive npm auth for this VM user.
umask 077
touch "${HOME}/.npmrc"
if grep -q '^//registry.npmjs.org/:_authToken=' "${HOME}/.npmrc" 2>/dev/null; then
sed -i 's#^//registry.npmjs.org/:_authToken=.*#//registry.npmjs.org/:_authToken=${NPM_TOKEN}#' "${HOME}/.npmrc"
else
printf "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}\n" >> "${HOME}/.npmrc"
fi
fi

echo "runtime_auth_injection=ok"
echo "has_ch1tty_smart_token=$([[ -n "${CH1TTY_SMART:-}" ]] && echo true || echo false)"
echo "has_chittymcp_token=$([[ -n "${CHITTYMCP:-}" ]] && echo true || echo false)"
echo "has_npm_token=$([[ -n "${NPM_ISSUED:-}" ]] && echo true || echo false)"
74 changes: 74 additions & 0 deletions scripts/setup-mcp-profiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail

# Render profile-specific MCP config snippets.
# Purpose: explicit split between ChatGPT MCP endpoints and non-ChatGPT MCP endpoints.

TARGET="${1:-all}" # chatgpt | claude | codex | all

render_chatgpt() {
cat <<'EOF'
{
"chatgpt_mcp": {
"ch1tty_smart_mcp": {
"url": "https://chatgpt.ch1tty.com/mcp",
"bearer_token_env_var": "CHITTYAUTH_ISSUED_CH1TTY_SMART_MCP_TOKEN"
},
"chittymcp": {
"url": "https://chatgpt.chitty.cc/mcp",
"bearer_token_env_var": "CHITTYAUTH_ISSUED_CHITTYMCP_TOKEN"
}
}
}
EOF
}

render_claude() {
cat <<'EOF'
{
"mcpServers": {
"ch1tty-primary": { "url": "https://mcp.ch1tty.com/mcp" },
"ch1tty-fallback": { "url": "https://mcp.chitty.cc/mcp" }
}
}
EOF
}

render_codex() {
cat <<'EOF'
[mcp_servers.ch1tty-smart-mcp]
url = "https://chatgpt.ch1tty.com/mcp"
bearer_token_env_var = "CHITTYAUTH_ISSUED_CH1TTY_SMART_MCP_TOKEN"

[mcp_servers.chittymcp]
url = "https://chatgpt.chitty.cc/mcp"
bearer_token_env_var = "CHITTYAUTH_ISSUED_CHITTYMCP_TOKEN"

EOF
}

case "$TARGET" in
chatgpt)
render_chatgpt
;;
claude)
render_claude
;;
codex)
render_codex
;;
all)
echo "# --- ChatGPT profile ---"
render_chatgpt
echo
echo "# --- Claude profile ---"
render_claude
echo
echo "# --- Codex profile ---"
render_codex
;;
*)
echo "Usage: $0 [chatgpt|claude|codex|all]" >&2
exit 2
;;
esac
14 changes: 7 additions & 7 deletions scripts/setup-mcp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ CHITTYCONNECT_URL=${CHITTYCONNECT_URL:-https://connect.chitty.cc}

# Prompt for auth token
echo ""
echo "You need a ChittyAuth token to connect to ChittyConnect."
echo "You need a ChittyAuth-issued token to connect to ChittyConnect."
echo "To get a token, visit: https://auth.chitty.cc/register"
echo ""
read -sp "ChittyAuth Token: " CHITTY_AUTH_TOKEN
read -sp "ChittyAuth Service Token: " CHITTYAUTH_ISSUED_CONNECT_SERVICE_TOKEN
echo ""

if [ -z "$CHITTY_AUTH_TOKEN" ]; then
if [ -z "$CHITTYAUTH_ISSUED_CONNECT_SERVICE_TOKEN" ]; then
echo -e "${RED}✗ ChittyAuth token is required${NC}"
exit 1
fi
Expand All @@ -118,7 +118,7 @@ echo -e "${YELLOW}[Step 4/6]${NC} Testing ChittyConnect connection..."

# Test API connection
HEALTH_CHECK=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $CHITTY_AUTH_TOKEN" \
-H "Authorization: Bearer $CHITTYAUTH_ISSUED_CONNECT_SERVICE_TOKEN" \
"$CHITTYCONNECT_URL/health")

if [ "$HEALTH_CHECK" != "200" ]; then
Expand Down Expand Up @@ -168,7 +168,7 @@ MCP_CONFIG=$(cat <<EOF
"args": ["$PROJECT_DIR/mcp-server.js"],
"env": {
"CHITTYCONNECT_URL": "$CHITTYCONNECT_URL",
"CHITTY_AUTH_TOKEN": "$CHITTY_AUTH_TOKEN",
"CHITTY_AUTH_SERVICE_TOKEN": "$CHITTYAUTH_ISSUED_CONNECT_SERVICE_TOKEN",
"ENABLE_STREAMING": "true",
"SESSION_PERSISTENCE": "true",
"PLATFORM": "$PLATFORM",
Expand Down Expand Up @@ -239,7 +239,7 @@ echo -e "${GREEN}✓ Created test-mcp.sh${NC}"
cat > "$PROJECT_DIR/.env.example" <<EOF
# ChittyConnect MCP Configuration
CHITTYCONNECT_URL=https://connect.chitty.cc
CHITTY_AUTH_TOKEN=your_token_here
CHITTY_AUTH_SERVICE_TOKEN=your_token_here
ENABLE_STREAMING=true
SESSION_PERSISTENCE=true
PLATFORM=desktop
Expand Down Expand Up @@ -294,4 +294,4 @@ echo " • Quick Start: QUICK_START.md"

echo ""
echo -e "${YELLOW}Need help? Visit: https://chitty.cc/docs${NC}"
echo ""
echo ""
8 changes: 6 additions & 2 deletions src/api/routes/discovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { Hono } from "hono";
import { ChittyOSEcosystem } from "../../integrations/chittyos-ecosystem.js";
import { MCP_TOOL_NAMES } from "../../mcp/tool-registry.js";
import { getServiceCatalogEntries } from "../../lib/service-catalog.js";
import { getServiceCatalogEntriesDynamic } from "../../lib/service-catalog.js";

export const discoveryRoutes = new Hono();

Expand Down Expand Up @@ -56,7 +56,7 @@ discoveryRoutes.get("/chitty.json", async (c) => {
? servicesData
: servicesData?.services || [];

const catalogEntries = getServiceCatalogEntries(env);
const catalogEntries = await getServiceCatalogEntriesDynamic(env);

const normalizeService = function(service) {
const name = service?.name || service?.id || "";
Expand Down Expand Up @@ -113,6 +113,8 @@ discoveryRoutes.get("/chitty.json", async (c) => {
mcp: `${mcpBase}/${service.sub}/mcp`,
// Direct MCP link: useful for clients that prefer the origin service
direct_mcp: `${service.url.replace(/\/$/, "")}/mcp`,
// Explicit service endpoint MCP (e.g., dispute.chitty.cc/mcp)
service_endpoint_mcp: `${service.url.replace(/\/$/, "")}/mcp`,
// Legacy API field kept for backward compatibility
api: `https://api.chitty.cc/${service.sub}/api`,
direct_api: `${service.url.replace(/\/$/, "")}/api`,
Expand Down Expand Up @@ -145,6 +147,8 @@ discoveryRoutes.get("/chitty.json", async (c) => {
session_management: true,
oauth_discovery:
`${mcpBase}/.well-known/oauth-authorization-server`,
oauth_authority: "https://auth.chitty.cc",
oauth_backend: "neon (via chittyauth facade)",
},
api: {
openapi_spec: "https://connect.chitty.cc/openapi.json",
Expand Down
8 changes: 5 additions & 3 deletions src/api/routes/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import { Hono } from "hono";
import { getServiceCatalog } from "../../lib/service-catalog.js";
import { getServiceCatalogEntriesDynamic } from "../../lib/service-catalog.js";

const servicesRoutes = new Hono();

Expand All @@ -13,7 +13,8 @@ const servicesRoutes = new Hono();
*/
servicesRoutes.get("/status", async (c) => {
try {
const statusChecks = getServiceCatalog(c.env).map(async (service) => {
const catalog = await getServiceCatalogEntriesDynamic(c.env);
const statusChecks = catalog.map(async (service) => {
try {
const response = await fetch(`${service.url}/health`, {
method: "GET",
Expand Down Expand Up @@ -60,7 +61,8 @@ servicesRoutes.get("/status", async (c) => {
servicesRoutes.get("/:serviceId/status", async (c) => {
try {
const serviceId = c.req.param("serviceId");
const service = getServiceCatalog(c.env).find((s) => s.id === serviceId);
const catalog = await getServiceCatalogEntriesDynamic(c.env);
const service = catalog.find((s) => s.id === serviceId || s.sub === serviceId);

if (!service) {
return c.json({ error: "Service not found" }, 404);
Expand Down
48 changes: 48 additions & 0 deletions src/lib/service-catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,54 @@ const SERVICE_ENTRIES = [
{ id: "chittytask", sub: "tasks" },
];

function normalizeMcpServiceCatalog(payload) {
// chittymcp index returns:
// { services: { finance: { path: "/finance/mcp", ... }, ... } }
const servicesObj = payload?.services;
if (!servicesObj || typeof servicesObj !== "object") return [];

const out = [];
for (const [sub, meta] of Object.entries(servicesObj)) {
const id = `chitty${sub}`;
const domain = "chitty.cc";
out.push({
id,
sub,
url: `https://${sub}.${domain}`,
mcpPath: meta?.path || `/${sub}/mcp`,
label: meta?.label || id,
});
}
return out;
}

/**
* Resolve service catalog from chittymcp authority when configured.
* Falls back to local static catalog if unavailable.
*/
export async function getServiceCatalogEntriesDynamic(env = {}) {
const authorityUrl =
env.CHITTYMCP_AUTHORITY_URL || "https://mcp.chitty.cc";

try {
const response = await fetch(authorityUrl, {
method: "GET",
signal: AbortSignal.timeout(4000),
});
if (response.ok) {
const payload = await response.json();
const normalized = normalizeMcpServiceCatalog(payload);
if (normalized.length > 0) return normalized;
}
} catch (error) {
console.warn(
`[service-catalog] chittymcp authority unavailable (${authorityUrl}): ${error.message}`,
);
}

return getServiceCatalogEntries(env);
}

/**
* Get the full service catalog including subdomain prefixes.
*
Expand Down
22 changes: 12 additions & 10 deletions src/middleware/oauth-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import { McpConnectAgent } from "../mcp/agent.js";
*/
export function createOAuthProvider(honoApp) {
return new OAuthProvider({
// Hostname-specific: only mcp.chitty.cc/mcp is OAuth-protected.
// connect.chitty.cc/mcp/* falls through to defaultHandler (Hono + API key auth).
apiRoute: "https://mcp.chitty.cc/mcp",
// Host-agnostic route so OAuth MCP can operate behind Cloudflare gateway,
// portal, and custom MCP hostnames that terminate to this Worker.
apiRoute: "/mcp",

apiHandler: McpConnectAgent.serve("/mcp", { binding: "MCP_AGENT" }),

Expand Down Expand Up @@ -128,13 +128,15 @@ async function handleAuthorize(request, env) {
// @canon: chittycanon://gov/governance#core-types
const safeClientId = (oauthReqInfo.clientId || "anonymous").replace(/:/g, "-");
const actorId = `mcp-client-${safeClientId}`;
const { redirectTo } = await env.OAUTH_PROVIDER.completeAuthorization({
request: oauthReqInfo,
userId: actorId,
metadata: {
client: clientInfo?.clientName || oauthReqInfo.clientId || "unknown",
authorizedAt: new Date().toISOString(),
},
const { redirectTo } = await env.OAUTH_PROVIDER.completeAuthorization({
request: oauthReqInfo,
userId: actorId,
metadata: {
client: clientInfo?.clientName || oauthReqInfo.clientId || "unknown",
oauthAuthority: env.CHITTYAUTH_URL || "https://auth.chitty.cc",
authBackend: env.CHITTYAUTH_PROVIDER_BACKEND || "neon",
authorizedAt: new Date().toISOString(),
},
scope: oauthReqInfo.scope || ["mcp:read", "mcp:write"],
props: {
userId: actorId,
Expand Down
7 changes: 5 additions & 2 deletions src/services/cloudflare-secrets-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ const PATH_TO_ENV = {
// Services
"services/chittyauth/jwt_secret": "JWT_SECRET",
"services/chittyauth/encryption_key": "ENCRYPTION_KEY",
"services/chittyauth/token_signing_key": "TOKEN_SIGNING_KEY",
"services/chittyauth/token_signing_key": "CHITTYAUTH_ISSUED_MINT_API_KEY",
"services/chittyauth/auth_salt": "AUTH_SALT",
"services/chittyconnect/service_token": "CHITTYCONNECT_SERVICE_TOKEN",
"services/chittyconnect/mcp_token": "CHITTYCONNECT_TOKEN",
"services/chittyconnect/mcp_token": "CHITTYAUTH_ISSUED_CH1TTY_SMART_MCP_TOKEN",
"services/chittyid/service_token": "CHITTY_ID_SERVICE_TOKEN",
"services/chittyid/token": "CHITTY_ID_TOKEN",
"services/chittyregistry/token": "CHITTY_REGISTRY_TOKEN",
Expand All @@ -69,6 +69,9 @@ const PATH_TO_ENV = {
"services/chittytrack/webhook_secret": "GITHUB_WEBHOOK_SECRET",
"services/chittymint/secret": "CHITTYAUTH_ISSUED_MINT_API_KEY",
"services/chittymint/service_token": "CHITTYAUTH_ISSUED_MINT_API_KEY",
"services/ch1tty-smart-mcp/token": "CHITTYAUTH_ISSUED_CH1TTY_SMART_MCP_TOKEN",
"services/chittymcp/token": "CHITTYAUTH_ISSUED_CHITTYMCP_TOKEN",
"integrations/npm/token": "CHITTYAUTH_ISSUED_NPM_TOKEN",
};

export class CloudflareSecretsClient {
Expand Down
Loading