AimYear

io.github.YahhiPublished by GitHub account Yahhiv0.4.2Updated Nov 1, 2026

Accountability partner in your agent: one yearly aim, one main step a day, a timer your agent runs.

VerifiedStreamable HTTPWeb executableProductivity & WorkflowData & Analytics

Overview

AI-generated overview

AimYear is a remote accountability server that lets an assistant hold one yearly goal, plan a daily main step, run a work timer and review where time went.

What you can do
  • Set and check one yearly goal, its levers and other projects, and record progress values
  • Build a path of milestones and concrete steps, and list the next open steps
  • Plan days and routines, edit or delete plan items, and set a day's tracking mode
  • Start, stop, split, add, edit and delete work sessions, and find untimed gaps
  • Review days, weeks and leaks, and read settings such as timezone and reminder targets
When to use it
Useful for people with one big yearly aim who keep postponing the work that moves it, such as indie developers who want an agent to plan a daily main step, time sessions and report weekly balance. It fits workflows where the assistant, not the user, drives planning and tracking.
Requirements
Remote Streamable HTTP endpoint at mcp.aimyear.com/mcp; nothing to install. Sign-in is OAuth 2.1 with Google, Apple or email, and guest accounts cannot be connected. A paid ChatGPT plan with developer mode is needed for ChatGPT; other MCP clients must support remote HTTP servers with OAuth.
Before you install
The server stores goals, plans, sessions and summaries in Google Cloud Firestore under the signed-in account, shared with the AimYear app. Tools write, replace and delete goals, plans, sessions and routines, so assistant mistakes change tracked history. Pro features are paid ($49/year or $8/month). Account and data deletion is by email request.

Installation

In SourceWeft

  1. Open AimYear in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.

Use with an agent

Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.

Read https://sourceweft.com/mcp/io.github.Yahhi%2Faimyear-mcp/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.

Read the install guide the agent follows

Other MCP clients

Add this to your client's mcpServers config.

{
  "mcpServers": {
    "aimyear": {
      "type": "http",
      "url": "https://mcp.aimyear.com/mcp"
    }
  }
}

README

AimYear MCP server

An accountability partner inside the AI agent you already use. Connect AimYear to Claude, Claude Code, ChatGPT or any MCP client, and your agent keeps one big aim for your year, plans one main step a day toward it, runs the timer while you work and writes down what actually got done. When you start comfortable work while the main step is still open, it asks you to do the step first.

Built for indie developers and makers who build all day but keep postponing the work that moves the goal: selling, talking to users, publishing.

  • Free: sign-in on any number of devices, aims, plans, routines, time tracking and reviews through your own agent. No AI runs on AimYear's side, so there are no AI costs.
  • Pro ($49/year or $8/month, with the coming phone app): notifications, reviews written by AimYear, AimYear's own AI coach and Google Calendar sync.

Website: aimyear.com · Setup guide: aimyear.com/connect.html · Privacy: aimyear.com/privacy.html · Contact: [email protected]

Connect (about a minute)

Endpoint: https://mcp.aimyear.com/mcp (Streamable HTTP, OAuth sign-in with Google, Apple or email). Nothing to install.

  • Claude (web, desktop, mobile): Settings → Connectors → Add custom connector → paste the endpoint → Connect, then sign in.
  • Claude Code: claude mcp add --transport http aimyear https://mcp.aimyear.com/mcp, then /mcp → aimyear → Authenticate.
  • ChatGPT (paid plans, developer mode): Settings → Apps & Connectors → Advanced settings → turn on Developer mode. Then Apps & Connectors → Create: name AimYear, URL the endpoint above, authentication OAuth → Create, then sign in.
  • Other MCP clients: add the endpoint as a remote (HTTP) server with OAuth.

Then say: "Help me set my aim for this year". The agent checks that the aim is countable, builds the path of stages, and from then on: "plan my day", "start the timer", "how was my week?".

The skill that teaches an agent the method is in skills/aim/SKILL.md.

What happens to your data

The server stores what your agent saves through the tools (aim, path, plans, sessions and their summaries) in Google Cloud Firestore. Your conversation stays with your agent's provider; AimYear only receives the tool calls. Logs keep the tool name, success and timing, never argument values. To delete your account and data, email [email protected]. Details: privacy policy.

Tools (43)

Names say what a tool does: get_ reads one thing, list_ reads several, find_ looks for problems, set_ replaces a whole list, add_ / edit_ / delete_ change one entry.

GroupTools
Aimget_goal, check_goal_draft, set_goal, record_progress, set_levers, set_projects
Pathget_path, set_milestones, edit_milestone, add_steps, edit_step, delete_step, next_steps
Weekget_commitments, set_commitments, get_capacity
Dayget_planning_context, get_plan, set_plan, edit_plan_item, delete_plan_item, set_day_mode, get_routines, set_routines
Timer and sessionsget_now, start_session, add_note, split_attention, stop_session, stop_timer, add_session, edit_session, delete_session, list_sessions, find_gaps
Reviewssummarize_day, summarize_week, find_leaks
Settings and devicesget_settings, set_settings, add_device, list_devices, delete_device

0.4.2 adds the hard-first check: set_plan warns when nothing in the day is on a hard lever, and add_steps / set_levers warn when a stage starts with preparation before the first hard-lever step. The server instructions tell every agent the same rule.

0.4.0 adds routines: small steps that repeat on fixed weekdays ("talk with one person on Reddit"). The server adds them to each matching day's plan, so every device and agent sees the same checks; get_now lists the open ones and summarize_week counts them.

0.3.0 renamed most tools (0.2.x names stop working) and added the editing tools: edit_session, delete_session for finished sessions, edit_milestone, delete_step, edit_plan_item, delete_plan_item, set_projects, and get_settings / set_settings with the timezone.

Protocol details

Stateless Streamable HTTP, POST only. Sign-in is OAuth 2.1 with PKCE and dynamic client registration. The sign-in page uses the same Firebase project as the AimYear app, so the app and every connected AI share one account and one history. Guest (anonymous) accounts cannot be connected. Every tool acts as one signed-in person; see docs/ACCESS.md.

Without a token, the server describes itself (initialize, tools/list, prompts/list, ping); everything else answers 401 with a WWW-Authenticate header pointing at /.well-known/oauth-protected-resource/mcp.

Retry-safe writes for apps (0.7)

The server is the source of truth, and a phone may deliver a request long after the tap, or twice. So stop_session, start_session, add_session, add_note, add_steps and delete_session take an optional op_id: an id the app mints before its first attempt and reuses on every retry. The write is applied once; a retry gets the first answer back and writes nothing. Applied ops live in users/{uid}/ops/{op_id} (tool, sessionId, result, at, expiresAt) and are written in the same Firestore transaction as the change they record. Give the ops collection a TTL policy on expiresAt (30 days) in the Firestore console.

stop_session takes end_at and start_session takes start_at (ISO 8601) so a stop queued offline at 14:05 and delivered at 16:40 records 14:05. start_session also takes the app's own session_id (create-if-absent). A request with op_id, end_at or start_at must name its session_id: the server never guesses which session a replay means. Without them, an agent's stop_session / delete_session acts only on its own connection's single running session, and otherwise lists the running sessions with ids.

Two devices can stop one session; the result is the same whatever order the stops arrive in: the earliest end wins (once anyone stopped it, it was not running), done wins over any other outcome, the first summary stays and a different later one goes to the notes. Every stop is kept in the session's stops[], and the answer says applied: ended | shortened | recorded.


The rest of this file is for working on the server itself. You don't need it to use AimYear.

How it is built

FileWhat it does
src/http.tsThe Express app: the MCP endpoint, the OAuth routes, the menu bar endpoints.
src/access.tsOne table: every MCP method, tool and route → public / auth / admin, with the reason. Every gate reads it.
src/oauth.tsOAuth storage in Firestore (codes and tokens stored as SHA-256 hashes) and the sign-in/consent page. The protocol itself is the MCP SDK's mcpAuthRouter.
src/calllog.tsOne structured log line per MCP call: method, tool, ok, error code, latency, user, client. Never argument values.
src/tools.tsTool registration, annotations (read-only / destructive / idempotent) and the server instructions.
src/service.ts, src/domain/The rules: goal rubric, classification, attention, plans, sessions.
src/surface.tsThe surface fingerprint behind surface.lock.json.
.githooks/pre-commitRegenerates docs/ACCESS.md from src/access.ts and stages it on every commit, so the committed table can't drift (npm ci points core.hooksPath at it through the prepare script; test/access.test.ts still fails on a stale file).

Data lives in Firestore under users/{uid}/aim/{goal,path,profile}, users/{uid}/days, users/{uid}/sessions, users/{uid}/blocks, users/{uid}/ops (applied op_ids, TTL on expiresAt); OAuth under oauth_clients, oauth_requests, oauth_codes, oauth_tokens; reminders in pending_notifications (claimed by a transactional delete before each push).

Run locally

bash
npm install && npm run build# stdio, one person:claude mcp add --scope user aimyear -e AIMYEAR_UID=<firebase uid> -- node $PWD/dist/index.js# HTTP:AIMYEAR_TOKENS="sometoken=<uid>" PORT=8787 node dist/index.js --http

Credentials: a Firebase admin service account at ~/.aimyear/service-account.json (or AIMYEAR_SERVICE_ACCOUNT=<path>; falls back to Application Default Credentials).

Env: AIMYEAR_UID (stdio), AIMYEAR_TOKENS (HTTP: static token=uid pairs for Claude Code scripts, the menu bar app and the scheduler), AIMYEAR_BASE_URL (the public URL; the OAuth issuer), AIMYEAR_ALLOWED_ORIGINS (extra browser origins), AIMYEAR_WORKPLACE, AIMYEAR_TZ.

Tests

bash
npm test                  # access table guard, anonymous gate and batch smuggling, OAuth discovery,                          # annotations, Origin, version negotiation, surface lock, domain rulesnpm run test:conformance  # a client's path through the real HTTP entry point: initialize and                          # tools/list without a token, then every tool with a throwaway user                          # in the real Firestore; cleans upCONFORMANCE_URL=https://mcp.aimyear.com/mcp npm run test:conformance   # read-only tools, livenpm run test:oauth        # register → consent → real Firebase ID token → code → token (PKCE) →                          # tool call → refresh rotation, plus every refusal; cleans upnpx tsx test/smoke.ts     # the service end to end against Firestore

When you add a tool: give it an entry in src/access.ts (npm test fails without one), run npm run access-doc, bump src/version.ts, run npm run surface-lock.

Hosted (Cloud Run)

bash
gcloud run deploy aimyear-mcp --source . --project aimyear-app --region us-central1 --quiet

Env vars on the service: AIMYEAR_TOKENS, AIMYEAR_TZ, AIMYEAR_PROJECT, AIMYEAR_BASE_URL=https://mcp.aimyear.com. mcp.aimyear.com is a Cloud Run domain mapping (CNAME to ghs.googlehosted.com in Route 53) and is in Firebase Auth's authorized domains. Health: /health. The legacy URL https://<service>/c/<token>/mcp still works.

Call analytics: scripts/mcp-calls.sh [1d] prints calls per tool and the call sequences per person and client.

License

FSL-1.1-MIT: you can read, run, self-host and change this server, but not offer it as a competing commercial service. Each release becomes MIT two years after it is published. The hosted service is at https://mcp.aimyear.com/mcp.

Source: README.md at commit 99d96f4

Tools

51

get_goal

Get goal

The one yearly goal: kind (count or done), metric, starting value, current value, target, deadline, progress, levers (hard/comfortable) and other projects. Call first in most flows.

Takes no parameters.

check_goal_draft

Check goal draft

Check a goal that is not saved yet against the rubric (a countable metric with value today and target, or a done-or-not result you can check yes or no; deadline within 12 months; pace per week). Changes nothing. Returns problems[] and next_question: loop until problems is empty, then call set_goal. Prefer count; use done only when no honest number exists.

Parameterskindtitlemetrictargetbaselinedeadline

set_goal

Set goal

Save the yearly goal, or replace it with the next one. Rejects with problems[] if the rubric fails. Keeps levers and projects. area is which of the five life areas the goal belongs to.

Parameterswhyareakindtitlerequiredmetrictargetbaselinedeadlinerequired

record_progress

Record progress

Record the current value of the goal metric ("I now have 4 customers"), or of a countable milestone when milestoneId is given ("I have talked to 7 people"). For a done-or-not goal, 1 marks it done. One value per date: calling again for the same date corrects it. Says when the target is reached; a countable milestone that reaches its target is done.

ParametersdatevaluerequiredmilestoneId

set_levers

Set levers

Replace the list of levers: the recurring kinds of goal work. At least one must be kind=hard (the work the user avoids). keywords/repos are hints for classifying sessions from their task text. Pass existing ids to keep them.

Parametersleversrequired

set_projects

Set projects

Replace the list of other projects (paid work, other products, life routines), so their sessions count as other work or life, not goal work. Pass the whole list: leave one out to remove it, change its name or hints to edit it. Pass existing ids to keep them.

Parametersprojectsrequired

get_path

Get path

Milestones with kind, progress (recorded value or steps done), pace (ahead/on_pace/behind) and their steps. current_milestone_id is the one to work on.

Takes no parameters.

set_milestones

Set milestones

Replace the list of 2–6 ordered milestones under the goal, each countable (metric and target) or measured by its steps, with a due date before the goal deadline. Pass each existing id, or that milestone's steps are deleted. To change one milestone, use edit_milestone.

Parametersmilestonesrequired

edit_milestone

Edit milestone

Change one milestone: title, kind (count or steps), metric, target, due date or status. status=retired drops it without finishing; done or retired moves the path to the next milestone. Its steps are kept.

ParametersduekindtitlemetricstatustargetmilestoneIdrequired

add_steps

Add steps

Add concrete steps to a milestone. A step is an action that fits a few work blocks ("email 10 people from the waitlist"), not "do marketing". Abstract steps are rejected. Put a hard-lever step first; preparation only where it blocks it (warns otherwise).

Parametersop_idstepsrequiredmilestoneIdrequired

edit_step

Edit step

Change one step: title, status (todo/doing/done), order, lever, estimate.

ParametersordertitlestatusstepIdrequiredleverIdestimateMin

delete_step

Delete step

Delete one step from the path.

ParametersstepIdrequired

next_steps

Next steps

Open steps of the current milestone, hard-lever steps first: the candidates for today's main step.

Parameterscount

get_commitments

Get commitments

Recurring weekly time: commitments (school runs, training, classes), overhead (sleep, meals) and focus windows.

Takes no parameters.

set_commitments

Set commitments

Replace the list of recurring weekly commitments. days are ISO weekdays 1=Mon..7=Sun; use timesPerWeek instead when there are no fixed days. Pass existing ids to keep them.

Parameterscommitmentsrequired

get_capacity

Get capacity

Weekly time budget out of 168 hours: overhead, hours committed per area, focus windows, and what is still unaccounted for. Nothing is assumed.

Takes no parameters.

get_planning_context

Get planning context

Everything needed to plan a day: goal and pace, next_steps, yesterday's summary and undone items, week balance, days since a hard lever. Defaults to tomorrow. The agent plans; then call set_plan.

Parametersdate

get_plan

Get plan

The plan for a day (default today).

Parametersdate

set_plan

Set plan

Replace the plan for a day. Exactly one item must have isMainStep=true (the step toward the goal), ideally linked by stepId from next_steps and on a hard lever. Warns when no item is on a hard lever. Other items: paid work, family, workout, etc. with their area. Items that stay keep their done state.

Parametersdateitemsrequired

edit_plan_item

Edit plan item

Change one plan item: mark it done or not done, rename it, change its estimate, area or links, or make it the main step. done=true also completes its linked path step. For a weekly routine, count says how many units this tick did (talked to 2 people today). For a routine, level minimum says only its bad-day minimum was done: it counts for the day, reported apart from full.

ParametersareadatedonecountleveltitleitemIdrequiredstepIdleverIdprojectIdisMainStepestimateMin

get_routines

Get routines

Routines: small steps that repeat on fixed weekdays, e.g. "talk with one person on Reddit", or that count up to a number a week, e.g. "talk to 5 people a week", each with an optional bad-day minimum. The server adds them to each matching day's plan, so every device and agent sees the same checks. With no routines yet, returns ready-made starters for the aim's area to offer as they are.

Takes no parameters.

set_routines

Set routines

Replace the list of routines: small steps that repeat on fixed weekdays. From today on, each one appears as a plan item on its days; tick it with edit_plan_item, skip it for one day with delete_plan_item. set_plan keeps them. days are ISO weekdays 1=Mon..7=Sun, default every day. perWeek makes it a weekly count: each tick adds its count (default 1), and once the week reaches perWeek it leaves the plan until Monday. Link it to the goal with leverId. minimum is the small version for a bad day ("read one thread" for "talk to one person"). Pass existing ids to keep them.

Parametersroutinesrequired

delete_plan_item

Delete plan item

Remove one item from a day's plan. A routine item is skipped for that day only.

ParametersdateitemIdrequired

set_day_mode

Set day mode

Tracking for a day: full (everything recorded, the default) or off (timer and reminders only, nothing stored, no plan required).

Parametersdatemoderequired

get_now

Get now

What is happening now: running sessions with attention shares, the timer, today's main step, today's minutes, the streak and a one-line nudge.

Takes no parameters.

start_session

Start session

Start a session of work and, if none is running, the timer. Returns a verdict: ok | stop | plan_first. On "stop" NO session is created: relay the message, ask the user; if they insist, call again with override=true. Several sessions may run at once (two terminals) and share attention by weight. Apps replaying a queued start pass op_id, their own session_id and start_at; an agent leaves all three out.

Parametersareataskrequiredop_iditemIdstepIdweightleverIdminutesoverridestart_atdirectionexclusive+3 more

add_note

Add note

Add a one-line note to a running session (this connection's, or session_id). May classify a session that was unclassified.

Parametersnoterequiredop_idsession_id

split_attention

Split attention

Set attention weights of the running sessions at once, e.g. {"abc": 0.7, "def": 0.3}. Applies to the whole session. For a finished session use edit_session.

Parameterssharesrequired

stop_session

Stop session

Stop a running session with a summary and outcome. Pass session_id from get_now; without it, only this connection's single running session is stopped, never another one. The timer keeps running for other sessions. outcome=done completes the linked plan item or path step. Without end_at or op_id it refuses a session that already ended: use edit_session for that. With end_at (apps delivering a stop queued offline) a second stop on the same session is merged, whatever order they arrive in: the earliest end wins, done wins over any other outcome, the first summary stays and a different later one goes to the notes; the answer says applied: ended | shortened | recorded.

Parametersop_idend_atoutcomerequiredsummaryrequiredsession_idattention_share

stop_timer

Stop timer

Stop the timer ("pause", "stop", "stand up") and every running session with it. There is no paused state: resuming starts a new session. Pass summary and outcome to save them on the sessions it closes, or call stop_session first. Returns the break length.

Parametersoutcomesummary

add_session

Add session

Add past time that was not timed: minutes (ending now, or at end), or start+end as HH:MM on date. For calls, children, workout, paid work, or work done away from the agent. Apps replaying a queued entry pass op_id, so a retry does not count the time twice.

Parametersendareadatetaskrequiredop_idstartitemIdstepIdweightleverIdminutesexclusive+1 more

edit_session

Edit session

Fix a session, running or finished: start and end (HH:MM on date; an end before the start means it ran past midnight), task, area, project, lever, step, plan item, summary, outcome, attention share, weight. It is classified again after a change. Use it when a timer was forgotten or a session was labelled wrong.

ParametersendareadatetaskstartitemIdstepIdweightleverIdoutcomesummaryexclusive+3 more

delete_session

Delete session

Delete a session, running or finished, so it no longer counts. Pass session_id from get_now or list_sessions; without it, only this connection's single running session is deleted, never another one.

Parametersop_idsession_id

list_sessions

List sessions

Sessions in a date range (default the last 7 days), with ids, optionally filtered by area or project.

ParameterstoareafromprojectId

find_gaps

Find gaps

Untimed stretches of 20+ minutes between the first and last session of a day. Ask the user what happened there.

Parametersdate

summarize_day

Summarize day

Full summary of a day: wall and attention minutes per area, classification and lever, sessions, gaps, overlaps, overrides.

Parametersdate

summarize_week

Summarize week

A week, Monday to Sunday (to today for the current week): main-step days, goal share, hard share, area balance, overrides, streak, milestone pace.

Parametersdate

get_insights

Get insights

Week (the last 7 days) or month (the last 30 days) ending on to (default today): minutes per day on the aim, other work and life; hard vs comfortable share of aim time; an hour × weekday heatmap of aim minutes (6:00–23:00, Mon–Sun) with its hard part and the peak; main-step days; and comebacks (active days after 2+ days away, over 90 days). Wall minutes of finished sessions, as the app shows them.

Parameterstorange

find_leaks

Find leaks

Where time leaks: comfortable goal work while the main step waited, overrides, unplanned work, gaps, switching, missed main steps, days without a plan. Default the last 7 days.

Parameterstofrom

get_settings

Get settings

Timezone, where reminders go (desktop or mobile), timer settings, and which networks get progress posts (post_to).

Takes no parameters.

set_settings

Set settings

Change any of: timezone (IANA name, e.g. Europe/Madrid; it sets where each day starts and ends), workplace for reminders, stand-up interval workMin, breakMin, longBreakEvery, longBreakMin, standup on/off, post_to (networks for progress posts).

Parameterspost_tostandupworkMinbreakMintimezoneworkplacelongBreakMinlongBreakEvery

add_device

Add device

Register a device token so server reminders (stand up, break over) can reach it. The phone app registers itself; use this for testing or a manual token.

Parameterslabeltokenrequiredplatformrequired

list_devices

List devices

Which devices can receive reminders, and when each was last seen.

Takes no parameters.

delete_device

Delete device

Remove a device so it stops receiving reminders.

Parameterstokenrequired

list_agents

List agents

Agents connected to this AimYear account (Claude, ChatGPT, Claude Code, the AimYear app…): name, client_id, when it connected and when it was last active. current marks the one calling.

Takes no parameters.

delete_agent

Delete agent

Disconnect one agent: its sign-in for this account is deleted and it can no longer read or change anything until it connects again. Only on the user's explicit request. client_id from list_agents.

Parametersclient_idrequired

add_social_account

Add social account

Connect WIP (wip.co) or Bluesky so AimYear can post the day's progress there when the user says Post (Pro). WIP: api_key from wip.co settings. Bluesky: handle and an app password (Settings → Privacy and security → App passwords), never the account password. The login is checked, stored encrypted and never shown again. The app's connect screen usually calls this; if the user pastes a key into the chat, use it once and do not repeat it.

Parametershandleapi_keynetworkrequiredapp_password

list_social_accounts

List social accounts

Connected networks (handle, since when), which of them get progress posts, and whether the user has Pro. Never returns any key or password.

Takes no parameters.

delete_social_account

Delete social account

Disconnect a network and delete its saved login. Posts already made stay there.

Parametersnetworkrequired

get_post_draft

Get post draft

The day's progress post drafted from the record (aim and progress, main step done or not, minutes on the aim and on hard work, routines, comebacks), at most 300 characters, ending with aimyear.com; and per network whether it is ready, already posted, or skipped because the user already posted there that day. Posts nothing. Pro.

Parametersdate

add_post

Add post

Post the day's progress, only after the user explicitly said to post. text is the draft as the user edited it (default: the draft from get_post_draft). Goes to the networks chosen in settings, or the ones given. A network where the user already posted that day themselves is skipped. Each day goes to each network once: a retry never posts twice. Today or yesterday only. Pro.

Parametersdatetextnetworks

Version history

1
  1. v0.4.2LatestOct 18, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 daysRequired25 pts
  • Auth declaredRequired10 pts
  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts

    Declare a license in the repository or the registry entry.