tipee
Skill1 starsChanged 15 days agoMITView on GitHub
Working on tipee-tools (Effect 4 core client, MCP server, Claude Code plugin, MSW test kit) — tipee access rights, API quirks vs. its docs, guardrails. Use whenever a task touches tipee, shifts, absences, or this repository's packages.
- Reads credentials
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 40
The API key is a secret: only in packages/mcp/.env or the Claude app's own settings, never in chat, commits or CI.
- Very specific: line 153
Run pnpm spec:refresh locally (without --bump) to do the same by hand.
- Very specific: line 190
No build step: Node 24 runs TypeScript directly (imports need the .ts extension, no enums).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
tipee API via tipee-tools
tipee is a Swiss HR tool (employees, shifts, absences, activities). This
repo exposes its API to agents, every operation but the few in EXCLUDED,
for any tipee instance: @tipee-tools/core (the HttpApi generated
from tipee's OpenAPI document, the derived client, the operation
catalogue, errors), @tipee-tools/mcp (one MCP tool per operation, built
from the catalogue, a *_many batch tool for a few, plus check_setup and
update_plugin), and the Claude
Code plugin in plugins/tipee that bundles the server. The user-facing
skill lives in plugins/tipee/skills/tipee; this one is for developing
the repo.
Guardrails
- Writes exist now. Which tools a user gets, and which need approval, is
decided in the Claude client; the server only annotates reads as read-only
and every write but a create as destructive, and each write's description
asks Claude to confirm first (Claude Desktop never loads the plugin's
skill). Don't add server-side gates or deny lists.
The one exception:
EXCLUDEDinpackages/core/scripts/spec.tsleaves operations out of the generated API.resources.grant-rolesandresources.revoke-roleswould let Claude raise anyone's rights, its own integration's included;credentials.update-must-change-passwordis account security;schedule-templates.deletewould rewrite history (next point). The last two are still in tipee's preview and listed ahead: a route the document lacks is skipped. A test keeps them all out. - Shift templates are history. Old templates are referenced by past plannings; never delete or modify them (that rewrites who worked when). A new need means a new template.
- Personal data. Schemas are generated from the API document and pass through what tipee returns; tipee redacts what the integration may not see. Never commit real responses — fixtures are anonymised (the fictional company "Acme").
- The API key is a secret: only in
packages/mcp/.envor the Claude app's own settings, never in chat, commits or CI. Rotate it in the tipee admin if it leaks.
Running the server
pnpm mcp runs it on stdio from source; configuration in packages/mcp/.env
(TIPEE_INSTANCE, TIPEE_API_KEY). Plugin users get the prompt from Claude
Code instead. claude --plugin-dir ./plugins/tipee loads the plugin for
development; pnpm build refreshes the bundle it runs.
Getting API access (the trap)
The API is off until an admin with the "Responsable API" role turns it on
at /admin/instance/integrations/; integrations, their keys and their
rights are then managed at /hr-core/integrations. The full rights list
lives in plugins/tipee/skills/tipee/roles.md.
The key belongs to an integration — a service account created like an
employee, given roles. Until it has the authorization
Configurations générales → "Se connecter avec des applications externes",
every call answers HTTP 401 {"message":"Tipee.api.token_rights_missing"}
(not 403 as the docs say). No Planning role fixes that, and regenerating the
key doesn't help. A wrong key gives "Le jeton fourni est invalide.".
Reads also need: Planning → "Accéder au module Planning" + "Voir les
plannings"; Cœur RH → "Accéder au module Cœur RH" + "Voir les
collaborateurs". Writes need Planning → "Planifier". Pay-related fields
come back as {"redacted": "forbidden"}.
API facts that differ from the docs
Docs: https://api.tipee.ch/ (index https://api.tipee.ch/llms.txt, OpenAPI
https://api.tipee.ch/openapi/<version>.json). Every endpoint is
POST https://<instance>.tipee.net/api/... with a JSON body and headers
Authorization: Bearer, Tipee-Version: <version> (TIPEE_API_VERSION,
the vendored document's version), Accept: application/json.
- Date-time intervals carry no seconds:
2026-09-07T23:00/2026-09-08T00:00(the docs' examples show seconds). Template hours are23:00/PT1H. - Date ranges can be open:
2026-08-01/-,-/2018-12-31. - Any value the integration may not see becomes
{"redacted": "forbidden" | "confidential"}in place of a string, number or object. - Durations can carry negative parts (
PT-15M); some templates havePT0S. - Pagination (
resources.list): sendpagination: {limit, next_token}explicitly (next_token: nullfirst), explicitorders({key: "resource.attribute", attribute: "last_name", direction: "asc"}), and identical filters/orders on every page or tipee answers 422. The cursor can be non-null on the last full page (the next page is empty). - Team filter:
{key: "resource.team", value: {teams: [id], recursive: true}}. - Rate limits are generous (500-token bucket, 4/s). tipee guarantees that
an error status (4xx or 5xx) changed nothing. Every operation is a POST,
so the client retries reads (
*.list,*.show*) on HTTP 408, 5xx and network failures, and anything on 429, 503 (maintenance) and 508 (a concurrent change, which tipee says to replay), waiting at least the Retry-After up to 10 s (resendableinTipeeClient.ts). A write whose answer was lost (a timeout, a broken connection, a gateway's 502 or 504) may have been applied: it is never sent twice and its error says to read it back. Each attempt times out after 20 s and a whole call after 50 s (MCP clients give up near 60 s); an attempt that timed out is not sent again. - Bulk writes: Claude used to make one call per day for a month of shifts.
whentakes a date interval or an RRULE, and the deletes take many ids; the descriptions, the instructions and the plugin skill say so. For edits that differ per item,BATCHEDoperations (packages/mcp/src/Batch.ts) get a<name>_manytool: up to 50 items, sent one after another throughinvoke, stopped at the first failure (tipee's per-request atomicity makes the report exact) or after 30 s, each item recorded as a call of its operationviathe batch. Add an operation there only when telemetry shows bulk use. *.tipee.nethas a wildcard DNS record: a mistyped instance never fails with ENOTFOUND but answers HTTP 410{"error":"instance_not_found"}.- Error messages quote tipee's whole answer and the HTTP status: never trim or paraphrase what tipee said, the agent acts on it.
- Versions are date-based and supported ≥ 6 months after the next release; subscribe to the changelog on https://api.tipee.ch/.
Generated code
packages/core/spec/ holds tipee's OpenAPI document (one file, the pinned
version). pnpm generate runs @effect/openapi-generator on it, through a
JSON Patch built in packages/core/scripts/generate.ts: mark every request
body required (tipee wants a JSON body even when empty); add to a few
descriptions what tipee's document leaves out (pagination, the integration
kind, the timecheck date range, an absence's percentage, a schedule create's
empty answer) and name tools instead of routes; add orders and
pagination to the required keys of ListResourcesQuery; let map-typed
response properties also be [] (PHP's empty map); drop example(s); turn
nested and/or filters into plain objects tipee validates; close request
objects with additionalProperties: false, which shortens their schemas but
refuses nothing (decoding drops unknown keys; the tools are Tool.Strict,
which refuses them); turn enums only answers carry into strings listing the
known values (tipee adds values within a version; request enums stay
closed); remove the excluded operations. Dropping examples, cutting filters
and closing objects keep the tool definitions small, since Claude Desktop
loads all of them into every chat. A description rewrite that no longer
changes anything fails the generation: tipee reworded the text, so the patch
needs a look. It writes packages/core/src/generated/TipeeApi.ts —
committed, never edited, freshness-checked by pnpm verify.
Keeping up with tipee: .github/workflows/spec-refresh.yml runs
pnpm spec:refresh --bump every Monday. The script (in
packages/core/scripts/refresh.ts) finds the newest stable version in
llms.txt, downloads its document over the vendored one (or, for a new
version, replaces the file and moves TIPEE_API_VERSION), regenerates,
rebuilds and prints the operations and schemas that changed. The workflow
opens or updates the PR (spec-refresh, or spec-move-<version> for a
new version), with the version bumped: minor when operations were added,
patch otherwise, and a needs-major label when one disappeared or was
renamed, since tool names are the public surface. The routine: refresh PR →
review the summary and the generated diff (fix or drop patches in
generate.ts, add a fixture for a new shape) → merge, which releases it.
Run pnpm spec:refresh locally (without --bump) to do the same by hand.
Operations.ts reads the generated HttpApi with HttpApi.reflect and
derives tool names from paths; the MCP package turns each entry into a
strict, titled Tool.dynamic. Failure messages end with their next step
(explain in Invoke.ts): Claude Desktop has no skill to look fixes up
in. Spec-versus-reality fixes belong in the patch, nowhere else.
Repository notes
ARCHITECTURE.mddescribes the design (Effect 4 core, an MCP server that the plugin bundles, the same bundle as the Claude Desktop extension, the repo as its own marketplace) and what is not planned. Check it before adding a distribution channel or a configuration source.- Effect 4 is stable, pinned exactly (see
packages/core/package.json); theeffect-v4skill in.claude/skillslists the idioms and v4 gotchas. Read Effect's sources inpackages/core/node_modules/effect/src(exact version; pnpm does not linkeffectat the root) and its agent docs inpackages/core/node_modules/effect/AGENTS.mdand.../effect/ai-docsrather than memory. - TypeScript 7 (native compiler) with
@effect/tsgo:pnpm installrunseffect-tsgo patch, after whichtscand the type-aware lint report Effect diagnostics (for example preferTypedSchemaDecoder). Fix them rather than silencing them; severities live in each package'stsconfig.json. - Dependencies: Renovate (
renovate.json) opens grouped weekly PRs for npm and GitHub Actions (pinned to digests), with a one-week cooldown;pnpm-workspace.yamlenforces minimumReleaseAge, no trust downgrades and no exotic sub-dependencies. Renovate moveseffectand the@effect/*packages (except@effect/tsgo) together as one group; check that PR against theeffect-v4skill.@effect/tsgo, oxlint, oxlint-tsgolint and TypeScript move together as the TypeScript toolchain. No Dependabot: its updater cannot run pnpm 12. - Releases: the version lives in
plugins/tipee/.claude-plugin/plugin.jsonandSERVER_VERSIONonly (the packages carry none, and core and mcp are private). A change to what ships bumps both;verify.ymlfails a change under a version already tagged, and on main releases an untagged version from the.mcpbit built. It also runsplugins/tipee/scripts/smoke.tson the committed bundle with Node 22.19. - No build step: Node 24 runs TypeScript directly (imports need the
.tsextension, no enums). The only build ispnpm build, which bundles the server intoplugins/tipee/server/(the plugin is the workspace package@tipee-tools/plugin) — committed, and checked for freshness bypnpm verify. - Tasks run through Turborepo (
turbo.json):check,test,buildper package, cached;transitnodes propagate source changes between workspace packages;lint:checkandformat:checkare root tasks. Run a single package withpnpm exec turbo run test --filter=@tipee-tools/mcp. - Tests:
@effect/vitest+ MSW.packages/core/test/handlers.tsis a fake tipee that enforces the real rules (401 bad key, 422 bad pagination) — assert on output, not on requests. Other packages import it from@tipee-tools/core/testing.it.effectruns on the test clock: wrap a call that retries insettled(test/answers.ts), which steps the clock until it finishes. OneTestClock.adjustis not enough: MSW answers on the real event loop, so the adjust can pass before a retry's sleep exists. pnpm fixapplies the oxlint/oxfmt fixers and rebuilds the bundle;pnpm verifyonly checks (CI and the pre-commit hook rely on that split); every disabled rule is commented inoxlint.config.ts(notablyunicorn/no-null: tipee's wire format needs literalnull).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

