Skip to content
Conduit

mcp server

open-aims

A self-hosted, keyboard-driven issue tracker for managing issues, projects, and cycles. Built as a fast workspace for small teams that want to run their own planning tool on their own infrastructure.

christianpasinrey/open-aimscommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 21, 2026

AIMS — Advanced Issue Management System

A self-hosted, keyboard-driven issue tracker for managing issues, projects, and cycles. Built as a fast workspace for small teams that want to run their own planning tool on their own infrastructure.

Stack

  • Laravel 13 + PHP 8.4 — modular monolith (app/Modules/*)
  • Vue 3.5 + Inertia 2 — single-page client, server-driven routing
  • Tailwind CSS v4 + shadcn-vue + Reka UI — design system
  • Vite + Wayfinder — typed routes generated from Laravel
  • MySQL — primary store
  • Pest / PHPUnit — backend tests

Local setup

Requires PHP 8.4, Composer, Node 20+, and MySQL 8.

# 1. Install dependencies
composer install
npm install

# 2. Configure environment
cp .env.example .env
php artisan key:generate

# 3. Edit .env — at minimum set DB_DATABASE, DB_USERNAME, DB_PASSWORD
# Create the database, then:
php artisan migrate --seed

# 4. Run dev server
composer dev   # runs PHP server, queue worker, Vite, and log tail concurrently
# OR
php artisan serve
npm run dev

The app is now reachable at http://localhost:8000.

Creating users

Registration is disabled on purpose: there is no register route and Features::registration() is not enabled in config/fortify.php. Accounts come from two places.

1. Invitations. An existing workspace member invites someone by email; the invitee sets their name and password when accepting. This covers normal onboarding, but it cannot help on a fresh install — there is nobody to send the first invitation.

2. The user:create command. For the first account, and as the escape hatch for anything the invitation flow cannot cover.

php artisan user:create

It is interactive by design, so the password never lands in shell history. It prompts for name, email and password, then optionally attaches the user to a workspace with a role (admin, member or guest). The workspace step is skipped when no workspace exists yet, which is the case when bootstrapping.

Validation reuses the application's own profile and password rules, so the command cannot create an account the app itself would reject. The user is created with email_verified_at already set — with no register route there is no way for them to trigger the verification mail.

Attach the user to a workspace unless you are bootstrapping. ResolveWorkspace never binds current.workspace for a user with no membership, so an account created without one can log in but lands on a broken app.

MCP access

AIMS exposes an MCP server so clients such as Claude Desktop can drive the workspace. Clients self-register at /oauth/register and then run the standard OAuth authorization code flow at /oauth/authorize, served by Laravel Passport.

Passport signs tokens with an RSA keypair that must be generated once per environment. storage/ is gitignored, so these keys are not carried by a deploy: a freshly deployed environment has none until you generate them.

Run every command below as the system user that owns the site, never as root. passport:keys writes the keys with mode 600 — readable only by their owner — so keys generated by root are unreadable by the PHP-FPM pool, and the OAuth flow fails just as surely as if they did not exist. The same applies to any other artisan command: running one as root leaves root-owned files in storage/ and bootstrap/cache that break the app later.

php artisan passport:keys
chmod 600 storage/oauth-private.key storage/oauth-public.key

Then confirm the ownership matches the PHP-FPM user, and fix it if you slipped and used root (on Plesk the group is usually psacln):

stat -c '%U:%G' storage/oauth-private.key storage/oauth-public.key
chown -R SITE_USER:psacln storage bootstrap/cache

Verify the key actually loads, without going through a browser:

php artisan tinker --execute="new League\OAuth2\Server\CryptKey('file://'.storage_path('oauth-private.key')); echo 'key OK';"

Run that check as the site user too. As root it prints key OK even when the keys are unreadable by PHP-FPM, which makes it look like the problem is elsewhere.

Regenerating the keys (passport:keys --force) invalidates every issued token, and connected MCP clients have to authorize again.

Troubleshooting LogicException: Invalid key supplied

A 500 on /oauth/authorize with this exception from league/oauth2-server means Passport was handed something that is neither a valid PEM nor an existing file. Passport resolves the key like this:

$key = str_replace('\\n', "\n", config("passport.{$type}_key") ?? '');

if (! $key) {
    $key = 'file://'.Passport::keyPath('oauth-'.$type.'.key');
}

So there are two causes. Either PASSPORT_PRIVATE_KEY / PASSPORT_PUBLIC_KEY are unset and storage/oauth-private.key does not exist — generate the keys — or they are set but their newlines are mangled, so the value is not a valid PEM. Fix the env values, or clear them and fall back to key files.

The exact message narrows it down:

MessageMeaning
Invalid key suppliedThe path does not exist, or the env value is not a PEM
Key path "…" does not exist or is not readableThe file exists but the PHP process cannot read it — check ownership
Unable to read key from file …The file exists and is readable, but its contents are not a valid key

GitHub integration

AIMS supports two GitHub flows:

1. OAuth — sign-in and account linking

Lets users sign in with GitHub and link their account to surface their PRs in issues.

In .env:

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_REDIRECT="${APP_URL}/gh/callback"

Create an OAuth App at https://github.com/settings/developers:

  • Homepage URL: ${APP_URL}
  • Authorization callback URL: ${APP_URL}/gh/callback

2. GitHub App — webhook ingestion

Connects a repo (or org) so that PR / commit references like LAM-123 auto-link to the matching AIMS issue.

In .env:

GITHUB_APP_ID=
GITHUB_APP_NAME=
GITHUB_APP_WEBHOOK_SECRET=
GITHUB_APP_PRIVATE_KEY_PATH=storage/keys/github-app.pem
GITHUB_APP_INSTALL_URL=https://github.com/apps/<your-app-name>/installations/new

Create the GitHub App at https://github.com/settings/apps/new:

  • Webhook URL: ${APP_URL}/gh/webhook
  • Webhook secret: same as GITHUB_APP_WEBHOOK_SECRET
  • Permissions: Pull requests: read, Contents: read, Issues: read, Metadata: read
  • Subscribe to events: Pull request, Push, Issues
  • Download the private key and save to storage/keys/github-app.pem

Map a repository to a workspace via env (one variable per workspace), e.g.:

GITHUB_APP_REPO_LAM=your-org/your-repo

Install the App on the configured repo using GITHUB_APP_INSTALL_URL. PRs and pushes that reference an issue key (e.g. LAM-42 in title, branch, or commit message) will be auto-linked.

Keyboard shortcuts

The workspace is keyboard-first.

ShortcutAction
Ctrl+K / Cmd+KOpen the search palette
G then IGo to issues
G then PGo to projects
G then CGo to cycles
G then HGo home
CCreate a new issue (when not typing in a field)
?Show the shortcuts cheat sheet

Useful commands

# Create a user account (registration is disabled by design)
php artisan user:create

# Generate the Passport keypair backing the MCP OAuth flow
php artisan passport:keys

# Run tests
php artisan test

# Format PHP
vendor/bin/pint

# Check / fix JS+Vue
npm run lint
npm run format
npm run types:check

# Production build
npm run build

Project layout

app/
  Console/Commands/             Artisan commands (user:create, ...)
  Models/                       Eloquent models (User, Workspace, Team, Issue, ...)
  Modules/
    Issues/                     Issues domain (controllers, resources, requests)
    Projects/                   Projects + milestones
    Cycles/                     Cycle planning + transitions
    Workspace/                  Settings, members, invitations
    Integrations/
      Github/                   OAuth, App service, webhook handler
resources/js/
  pages/                        Inertia pages — one per route
  components/repo/            Atomic issue-tracker components (StatusIcon, ProjectIcon, ...)
  components/ui/                shadcn-vue primitives
  composables/                  Reusable logic (keyboard shortcuts, theme, ...)
  layouts/                      Inertia persistent layouts
routes/
  web.php                       Public + authenticated routes
  settings.php                  /settings/* routes

License

Released under the MIT License.

↑↓ navigate openesc close