This repository is a pnpm + Turborepo monorepo for public @transcend-io/* TypeScript packages/.
The root package is private. Publishable packages live in packages/.
Install mise, then run:
mise trust mise.toml
mise install
mise run bootstrapmise.toml pins the Node version for local development, devcontainers, and CI. package.json
pins the exact pnpm version.
The repo uses oxc for linting and oxfmt for formatting.
- You should install the
oxc.oxc-vscodeextension to get the best experience. - You should disable ESLint and Prettier extensions, if you have them installed. You can disable them for this repository only by right-clicking the extension and selecting "Disable (Workspace)".
- Run
mise run bootstrapafter cloning and whenever dependencies change. - Make your change in the relevant package.
- Add a changeset when your change touches package code under
packages/. - Merge your feature PR into
main. - CI will automatically open a release PR.
- If you wish to release immediately, merge the release PR to publish to npm.
pnpm build: build workspace packages withtsdownpnpm test: run package tests with Vitestpnpm quality: run repo-wide verification checks and typecheckpnpm quality:fix: auto-fix formatting and lint issues where possible, then run the remaining quality checkspnpm changeset: create a changeset file
Additional checks:
pnpm typecheck: run TypeScript checks across workspace packagespnpm lint: runoxlintacross the repopnpm lint:fix: runoxlint --fixacross the repopnpm format: format the repo withoxfmtpnpm format:check: verify formatting withoxfmt --checkpnpm check:changeset: validate changeset coverage for publishable package changespnpm check:packages: validate shared package metadata and layout conventionspnpm check:exports: validate published package shape withattwpnpm check:publint: validate published package compatibility and packaging metadatapnpm check:deps: enforce dependency version policy
pnpm quality intentionally excludes pnpm check:changeset, since that check compares the current branch to its base and is mainly useful in pull request workflows.
Release and maintenance:
pnpm changeset:version: apply pending changesets to versions and changelogspnpm changeset:version:release: apply pending changesets and reformat the repopnpm release: build and publish packages
Use pnpm --filter or (pnpm -F for short) when you only need to work on one package:
pnpm -F cli startSee pnpm Filtering for more examples.
Note
- You do NOT need to manually version packages in your PRs. Instead, you need to create a changeset file via
pnpm changeset. - Your feature PR does NOT automatically publish to npm. Instead, CI opens a release PR after your feature PR is merged.
Stable releases are driven by Changesets, the Version workflow, and the Publish workflow:
-
Developers merge feature PRs with changesets into
main. -
The
Versionworkflow automatically opens a release PR, titled "Version Packages".Or, if there's already an open release PR, then that PR will be updated instead.
In a release PR:
- Each changeset file (at
.changeset/*.md) is turned into a change entry in each package'sCHANGELOG.mdfile, and the changeset file is deleted. - Package versions are bumped accordingly.
- Here's an example of a release PR.
- Each changeset file (at
-
When the release PR is merged, the
Publishworkflow publishes the packages to npm.
Thus, after you merge a feature PR, you can either:
- Merge the subsequent release PR to publish to npm if you want it released immediately, or
- Let a few feature PRs build up before merging a release PR and publishing to npm (this also allows you to split up your changes into multiple PRs without releasing).
Changesets record release intent.
Create one with:
pnpm changesetAdd a changeset when changes to a package under packages/ would require a new version to be published to npm.
scripts/check-changeset.ts enforces this on pull requests. It only requires coverage for changed publishable packages, and it ignores:
- private packages (i.e. packages with
"private": truein theirpackage.json) - package
README.mdchanges - test files
- generated
dist/output node_modules/and.turbo/- See
scripts/check-changeset.tsfor the full list of ignored files.
If a PR changes a publishable package, each changed publishable package must be mentioned in at least one changeset frontmatter block. Otherwise, CI fails.
Pull requests also get preview packages via pkg.pr.new, which you'll see as an automated bot comment in your PR. Use those when another repo or environment needs to test package changes before a stable release. Obviously, you should never use these preview releases in production.
This repo is set up for npm trusted publishing via GitHub OIDC. The publish workflow has
id-token: write, and normal publishing should not require a long-lived NPM_TOKEN. Each
published package still has to be configured in npm to trust this repository and the
publish.yml workflow filename.
If npm trusted publishing is not configured for a package, the publish workflow will build and version correctly but fail at publish time.
publish.yml also supports workflow_dispatch. Use that for reruns or recovery, not as the normal
release path.
This repo uses Turborepo, which caches package.json script outputs. Try running pnpm build twice to see the cache in action.
Note
Remote caching is not yet configured.
CI and release workflows can use Turbo remote caching when secrets.TURBO_TOKEN and
vars.TURBO_TEAM are configured. Local development does not require remote cache.
After a normal install from the repo root, Husky configures local Git hooks automatically.
pre-commit: runspnpm quality:fixand aborts if it updates tracked filespre-push: runspnpm test
These hooks are local guardrails. CI still runs the canonical repo checks on pull requests and releases.
If you install dependencies with scripts disabled, rerun pnpm run prepare from the repo root
before committing.
Shared dependency versions live in pnpm-workspace.yaml catalogs.
Guidelines:
- prefer
catalog:for shared toolchain dependencies - prefer
workspace:*for dependencies between local packages - keep additions consistent with the single-version policy enforced by
syncpack
On pull requests, GitHub Actions runs:
CI:CI / globaljob: runs all the standard commands across each package such asbuildandtestcommands.CI / <package-name>jobs: some packages may have their own jobs to add unique checks, such as theCI / cli, which ensures that generated CLI files are up to date.
Preview Releasesee Preview Releases for more.
New, reviewable commits pushed will dismiss previous pull request review approvals. As a public repository, we want to ensure that all changes are reviewed and approved by at least one team member.
This repo uses the GitHub Merge Queue feature to automatically merge pull requests when CI passes.
Transcend Team:
- See our HOWTO: Migrate a repo to transcend-io/tools.
- If this is a net-new npm package, it needs to first be created here, and it needs trusted publishing set up (see the HOWTO guide above for more on that). Once that is done, it should publish successfully the next time a release PR merges with a changeset for that package.
New packages should match the shape used in packages/sdk and packages/utils.
For a new package:
- Create
packages/<name>/package.json. - Add
tsconfig.json,tsdown.config.ts, andsrc/index.ts. - Add tests when the package has behavior worth validating.
- Add the package to the root
tsconfig.jsonreferences list. - Use
workspace:*for dependencies on other local packages. - Use
catalog:for shared tool dependencies.
Current package conventions:
- ESM-only packages with
"type": "module" - published entrypoints served from
dist/ @transcend-io/sourceexport condition for live source resolution inside the monorepobuild,test,typecheck, andcheck:exportsscripts in each package- publishable packages also provide a
check:publintscript pnpm check:packagesenforces shared package metadata, required package files, and roottsconfig.jsonreferences- publishable packages include
homepage,repository, andauthormetadata - released publishable packages keep a
CHANGELOG.md
MCP (Model Context Protocol) server packages live under packages/mcp/. They let AI agents interact with the Transcend platform. See the root README.md for a full list of packages and their purpose.
packages/mcp/
mcp-server-base/ # Shared infrastructure (GraphQL base, REST client, validation, types)
mcp-server-admin/ # Domain: org, users, API keys
mcp-server-assessment/
mcp-server-consent/
mcp-server-discovery/
mcp-server-dsr/
mcp-server-inventory/
mcp-server-preferences/
mcp-server-workflows/
mcp/ # Unified server that re-exports all domain tools
Each domain package provides a standalone CLI and can be installed independently. The unified mcp package composes all domains via ToolRegistry.
MCP servers read credentials from the environment. For local runs from this repository, use root secret.env (gitignored):
cp secret.env.example secret.env
# Edit secret.env — set OAuth vars (recommended) or TRANSCEND_API_KEYFor stdio / MCP client development, prefer OAuth credentials:
TRANSCEND_OAUTH_CLIENT_ID,TRANSCEND_OAUTH_CLIENT_SECRET,TRANSCEND_OAUTH_REDIRECT_PORT- Choose an available localhost port number and register redirect URI
http://127.0.0.1:{port}/callbackin the OAuth clients admin page ({port}is the number you chose)
See packages/mcp/README.md for the full setup guide.
Load variables into your shell before pnpm (from the repository root):
set -a && source ./secret.env && set +aOr run a built CLI via scripts/mcp-run.sh, which sources secret.env when the file exists:
./scripts/mcp-run.sh ./packages/mcp/mcp-server-consent/dist/cli.mjsUse the path to the dist/cli.mjs for the server you built. See each package README under packages/mcp/ for Turbo build filters.
Use pnpm --filter the same way as any other package. For build, prefer a Turbo filter with a trailing ... so mcp-server-base and other dependencies are built when needed:
pnpm exec turbo run build --filter="@transcend-io/mcp-server-consent..."
pnpm -F @transcend-io/mcp-server-consent test
pnpm -F @transcend-io/mcp-server-consent typecheckMCP servers support three authentication modes:
- OAuth (stdio, recommended) — set
TRANSCEND_OAUTH_CLIENT_ID,TRANSCEND_OAUTH_CLIENT_SECRET, andTRANSCEND_OAUTH_REDIRECT_PORT. Browser login runs on first tool call; tokens are session-only (in-memory). The issuer is auto-detected in production (api.transcend.io,api.us.transcend.io). - API key — set
TRANSCEND_API_KEYfor stdio (alternative to OAuth) or as HTTP default auth. When both API key and OAuth client ID are set, the API key takes precedence. - Session cookie — in HTTP mode, forward
Cookieandx-transcend-active-organization-idheaders for in-app dashboard auth
The AuthCredentials discriminated union represents OAuth tokens, API keys, and session cookies internally. See mcp-server-base/src/auth.ts for the type definition and resolveAuth() for header resolution logic.
OAuth stdio variables:
TRANSCEND_OAUTH_CLIENT_ID— enables OAuth mode when set (unlessTRANSCEND_API_KEYis also set)TRANSCEND_OAUTH_CLIENT_SECRET— client secret from the OAuth clients admin pageTRANSCEND_OAUTH_REDIRECT_PORT— localhost callback port; must match registered redirect URITRANSCEND_OAUTH_REDIRECT_HOST— loopback host (default127.0.0.1; use::1for IPv6)TRANSCEND_OAUTH_ISSUER— optional test-only override (requiresALLOW_TEST_OVERRIDES=1); production auto-detects regionALLOW_TEST_OVERRIDES— set to1to enable test-only URL overrides (TRANSCEND_OAUTH_ISSUER,TRANSCEND_DASHBOARD_URL). Unset or any other value is treated as disabled.
Optional overrides:
TRANSCEND_API_URL— GraphQL backend API base URL (default:https://api.transcend.io). Matches the CLI and main monorepo convention.SOMBRA_URL— Sombra REST API base URL (default:https://multi-tenant.sombra.transcend.io). Matches the CLI / SDK convention.TRANSCEND_DASHBOARD_URL— test-only dashboard base URL override (requiresALLOW_TEST_OVERRIDES=1)
For local development, define these in secret.env (copy from secret.env.example at the repository root). Do not commit secret.env.
HTTP transport variables (only used with --transport http):
TRANSCEND_HTTP_PORT— listen port (default:3000)TRANSCEND_HTTP_HOST— listen host (default:127.0.0.1)TRANSCEND_MCP_CORS_ORIGINS— comma-separated allowed CORS originsTRANSCEND_MCP_SESSION_TTL_MS— idle session timeout in milliseconds (default:1800000)
TRANSCEND_API_KEY=your-key pnpm -F @transcend-io/mcp-server-consent dev -- --transport http --port 3001The server listens at http://127.0.0.1:3001/mcp with a health check at /health. You can send JSON-RPC requests with curl:
# Initialize a session
curl -X POST http://127.0.0.1:3001/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}},"id":1}'For production deployment patterns (Docker, nginx, cloud), see packages/mcp/DEPLOYMENT.md.
- Create a new file under the domain's
src/tools/directory (e.g.src/tools/consent_new_tool.ts). - Export a
create*Tool(clients: ToolClients): ToolDefinitionfactory function following the existing pattern. - Register it in the domain's
src/tools/index.tsby importing and adding it to the returned array. - If the tool needs new input validation, add a Zod schema to
src/schemas.ts. - If the tool calls a new API endpoint, extend the domain's GraphQL mixin (
src/graphql.ts) or the shared REST client inmcp-server-base. - Add or extend tests in the domain package's
tests/directory. - The unified
mcppackage picks up the new tool automatically through itsToolRegistry.
These conventions are enforced by scripts/check-mcp-descriptions.test.ts and surface as CI failures:
- camelCase input fields. Tool names themselves stay snake_case (they are addressable identifiers), but every Zod input field must be camelCase. Snake_case fields are blocked by review and break LLM client tooling that assumes camelCase.
- Every field needs a
.describe(...)call. Descriptions are the only signal an LLM has for what an argument means.defineToolrecursively rejects any input field (at any nesting depth) with a missing or trivially short (< 8 chars) description, throwing at tool construction; the audit test enforces the same across every server in CI. - Pagination. Reuse
CursorPaginationSchemaorOffsetPaginationSchemafrom@transcend-io/mcp-server-baseinstead of redeclaringlimit/offsetper tool. The legacyPaginationSchemais deprecated.
Every MCP server's GraphQL operations are validated against the committed schema at compile time:
- Author operations with the generated
graphql()tag in each domain'ssrc/graphql.ts. The tag returns aTypedDocumentNode<Result, Variables>, whichTranscendGraphQLBase.makeRequestconsumes natively. Drift between an operation and the staging schema failstscrather than surfacing as a runtime error. - The schema lives at
schema.graphql(committed). Do not edit it by hand. Refresh it withpnpm graphql:refresh-schema(or let the scheduledRefresh GraphQL Schemaworkflow do it daily) and let CI flag any tools that break against the new shape. - After editing operations, run
pnpm codegento regenerate__generated__/artifacts. The artifacts are gitignored —pnpm build/pnpm typecheck/pnpm testinvoke the codegen task automatically through Turbo's dependency graph, so the only time you need to run codegen by hand is during local development.
schema.graphql is a committed snapshot of the staging GraphQL schema (api.staging.transcen.dental). It is the offline source of truth so local and CI builds stay hermetic. Refresh it when:
- Staging adds or removes a type/field that an MCP tool needs to consume.
- A failing
pnpm codegentraces back to a missing schema element.
To refresh:
- Run
pnpm graphql:refresh-schema. The script anonymously introspects staging, strips type/field descriptions (so internal prose never lands in this public repo), and writesschema.graphql. No API key or extra tooling is required. - Run
pnpm codegen && pnpm typecheckto confirm the new shape works for every operation, then commitschema.graphqlalongside any operation/test updates.
The scheduled .github/workflows/refresh-graphql-schema.yml workflow runs the same command weekly and opens a PR — let CI on that PR be your gate. Manual refreshes (e.g., when developing against a feature schema before it lands in staging) are fine; just make sure the resulting pnpm typecheck is green before pushing.
Heads-up: staging usually leads prod by days or weeks. If you author a tool that depends on a brand-new field, double-check the field has actually rolled out to
api.transcend.iobefore publishing the package — typed operations against fields that don't yet exist in prod will fail at runtime for customers.
MCP packages are publishable. Run pnpm changeset when your change modifies package code under packages/mcp/, just like any other publishable package.