AgentStack
MCP verified MIT Self-run

Golink

mcp-mudrii-golink · by mudrii

Production-grade LinkedIn CLI for humans and LLM agents

No reviews yet
0 installs
4 views
0.0% view→install

Install

$ agentstack add mcp-mudrii-golink

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Golink? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

golink

LinkedIn CLI for humans and LLM agents.

> Status: alpha / requires LinkedIn app credentials. The command tree, OAuth login, JSON envelope contract, retryable HTTP client, and LinkedIn REST adapter are implemented and tested. Live use requires a LinkedIn developer app with the documented scopes; without credentials you can still exercise every --dry-run and schema-validated envelope.

Overview

golink talks to LinkedIn through a transport-pluggable architecture. The default official transport wraps LinkedIn's Posts API and Community Management API with retry + rate-limit awareness. An unofficial transport slot exists behind --transport=unofficial but ships as a typed ErrFeatureUnavailable no-op until wired; --transport=auto currently resolves through the official adapter and preserves unsupported responses until fallback behavior is implemented.

Capability matrix

| Command | Implemented | Live-tested | Notes | |---|---|---|---| | auth login | ✅ | via httptest | Native PKCE S256 with loopback callback | | auth status | ✅ | ✅ | Reports unauthenticated when token is missing or expired; shows refresh expiry when available | | auth logout | ✅ | ✅ | Clears the keyring entry | | auth refresh | ✅ | via httptest | Exchanges refresh token for new access token; auto-runs within 5 min of expiry under a single-flight sidecar lock | | profile me | ✅ | ✅ | Reads cached OIDC claims from the session | | post create | ✅ | httptest | --dry-run previews the exact payload | | post create --dry-run | ✅ | ✅ | Schema-validated envelope, no network | | post list | ✅ | httptest | Defaults author to the session member URN | | post get | ✅ | httptest | Entitlement-gated by LinkedIn | | post delete | ✅ | httptest | --dry-run supported | | comment add | ✅ | httptest | --dry-run supported | | comment list | ✅ | httptest | | | react add | ✅ | httptest | --dry-run supported | | react list | ✅ | httptest | | | search people | Official transport: unsupported | ✅ | Returns ErrFeatureUnavailable by design | | doctor | ✅ | ✅ | Env vars, session state, userinfo probe, feature map, audit log state | | version | ✅ | ✅ | Reports build metadata | | batch | ✅ | httptest | Reads JSONL ops file, dispatches each op, streams JSONL results; supports --fail-fast, --strict, --concurrency, --resume | | approval list | ✅ | unit | List staged approval entries | | approval show | ✅ | unit | Show a single staged entry | | approval grant | ✅ | unit | Approve a pending entry | | approval deny | ✅ | unit | Deny a pending entry | | approval run | ✅ | unit | Execute an approved entry via transport | | approval cancel | ✅ | unit | Remove a pending entry | | --require-approval | ✅ | unit | Stage supported mutating commands for review (exits 3) | | social metadata ... | ✅ | httptest | Batch engagement read: comment count, reaction totals per type, comments-state for up to 100 post URNs in one call | | post schedule --at ... | ✅ | unit | Queue a post for later publication (client-side queue) | | schedule list / show / run / cancel / next | ✅ | unit | Manage the scheduled-post queue | | post edit | ✅ | httptest | Update commentary or visibility of an existing post | | post reshare | ✅ | httptest | Reshare an existing share with optional added commentary (requires Linkedin-Version >= 202209) | | post create --image | ✅ | httptest | Upload a local image and attach it to a new post | | plan post create / delete / edit / reshare / schedule | ✅ | unit | Generate a plan document without calling LinkedIn | | plan comment add | ✅ | unit | Generate a comment-add plan document | | plan react add | ✅ | unit | Generate a react-add plan document | | execute | ✅ | unit | Execute a golink.plan/v1 document via the normal Transport path | | org list | ✅ | httptest | List organizations where the session member is ADMINISTRATOR; accepts w_organization_social_feed and legacy w_organization_social | | post create --as-org | ✅ | httptest | Post as an organization; accepts w_organization_social_feed and legacy w_organization_social |

"httptest" means the code path is covered by an integration test against a local HTTP server that mimics the LinkedIn endpoint; a real request to api.linkedin.com requires your own developer app.

Organization posting

To post as a LinkedIn organization page you should request w_organization_social_feed on your LinkedIn app. golink also accepts the legacy w_organization_social scope for older apps. The session member must be an ADMINISTRATOR of the organization.

# List organizations you administer
golink --json org list

# Post as an org page
golink --json post create \
  --text "Exciting news from Acme Corp" \
  --as-org urn:li:organization:12345678

# Preview without sending
golink --json --dry-run post create \
  --text "Draft post" \
  --as-org urn:li:organization:12345678

# Plan for review, then execute
golink --json plan post create \
  --text "Queued org post" \
  --as-org urn:li:organization:12345678 > plan.json
golink execute plan.json

golink doctor reflects org list and post create --as-org availability based on whether an organization social write scope is in the session scopes.

Scheduling

LinkedIn has no native scheduled-post API. golink implements a client-side queue — scheduled posts are stored on disk and executed on demand:

# Queue a post for tomorrow 9am UTC
golink post schedule --at 2026-04-18T09:00:00Z --text "Good morning"

# Inspect the queue
golink schedule list
golink schedule next        # prints the earliest pending scheduled_at

# Execute past-due entries (typically invoked from cron)
golink schedule run --limit 20

golink does NOT run a daemon. Operators invoke schedule run via cron, launchd, systemd-timer, or an agent loop. Queue location: $GOLINK_SCHEDULE_DIR (override), else $XDG_STATE_HOME/golink/schedule/. --image paths must be absolute because they resolve at run time. --require-approval is not supported on scheduled posts in this release.

Installation

go install github.com/mudrii/golink@v26.05.13

Requires Go 1.26.3+.

Homebrew (tap lives in this repo's Formula/ directory):

brew tap mudrii/golink https://github.com/mudrii/golink
brew install mudrii/golink/golink

For full product, dependency, and LinkedIn Developer app setup instructions, see [LINKEDIN_SETUP.md](./LINKEDIN_SETUP.md).

Quick start

# Set your LinkedIn app client ID
export GOLINK_CLIENT_ID=your_client_id

# Authenticate (opens the system browser, waits on loopback)
golink auth login

# Everything works in JSON mode for scripts and agents
golink --json auth status
golink --json auth refresh   # manually exchange a refresh token for a new access token
golink --json profile me
golink --json post create --text "Hello from golink" --visibility PUBLIC
golink --json post list --count 5

Non-interactive / agent mode

Without a TTY, all interactive prompts are disabled and required inputs come from flags:

golink --json post create --text "Hello" --visibility PUBLIC
golink --json comment add urn:li:share:123 --text "nice"
golink --json react add urn:li:share:123 --type LIKE
golink --json post list --count 10

Use --dry-run to preview the exact request payload without sending it. Supported on every mutating command (post create/delete/edit/reshare/schedule, comment add, react add, execute).

Output modes

golink supports five output modes via --output:

| Flag | Mode | Description | |---|---|---| | (default) | text | Human-readable plain text | | --json or --output=json | json | Full JSON envelope, schema-validated | | --output=jsonl | jsonl | One JSON object per line; list commands emit one item per line, scalar commands emit a single envelope line unless the command is intentionally multi-event (for example auth login) | | --output=compact or --compact | compact | Stripped envelope (no command_id, generated_at, rate_limit); useful for LLM context budgets | | --output=table | table | Tabwriter-based columnar output for list commands; scalar commands fall back to text |

# Schema-validated JSON (agent contract — byte-for-byte stable)
golink --output=json post list --count 5
golink --json post list --count 5   # identical to above

# Compact — lower token cost for LLM pipelines
golink --compact auth status
golink --output=compact post list --count 5

# JSONL — one object per line, pipeable to jq / stream processors
golink --output=jsonl post list --count 10 | jq '.text'

# Table — human-readable columns
golink --output=table post list --count 10
golink --output=table react list urn:li:share:123

Schema validation: only --output=json (and legacy --json) is validated against schemas/golink-output.schema.json. Compact, JSONL, and table modes are lossy renderings of the same data.

Precedence: --compact > --output > --json > text default. Using both --compact and --output= is a validation error.

Transport modes

| Mode | Flag | Behavior | |---|---|---| | Official | --transport=official (default) | LinkedIn REST APIs with OAuth bearer token, retry on 429/5xx, rate-limit header parsing | | Unofficial | --transport=unofficial | Stub until a concrete adapter lands; requires --accept-unofficial-risk | | Auto | --transport=auto | Official first, best-effort fallback |

Configuration

| Source | Priority | |---|---| | CLI flags | Highest | | GOLINK_* env vars | | | ~/.config/golink/config.yaml | | | Defaults | Lowest |

Environment variables

| Variable | Required | Description | |---|---|---| | GOLINK_CLIENT_ID | Yes | LinkedIn app client ID (used for auth login and auth refresh) | | GOLINK_CLIENT_SECRET | Only when GOLINK_AUTH_FLOW=oauth2 | LinkedIn app client secret for standard OAuth 2.0 token exchange. Do not store it in the repo. | | GOLINK_AUTH_FLOW | No | pkce (default) uses LinkedIn native PKCE; oauth2 uses the standard confidential-client OAuth endpoint with GOLINK_CLIENT_SECRET and a fixed GOLINK_REDIRECT_PORT. | | GOLINK_AUTH_SCOPES | No | Override login scopes (space- or comma-separated). Use when LinkedIn rejects OpenID scopes for native PKCE. | | GOLINK_MEMBER_URN | No | Manual member URN fallback, e.g. urn:li:person:abc123, used when OIDC userinfo is unavailable. | | GOLINK_API_VERSION | No | Linkedin-Version header, e.g. 202604 | | GOLINK_REDIRECT_PORT | No | Preferred OAuth loopback port; 0 picks any free port | | GOLINK_JSON, GOLINK_TRANSPORT, GOLINK_OUTPUT | No | Preflight overrides for --json / --transport / --output | | GOLINK_IDEMPOTENCY_PATH | No | Override idempotency store path | | GOLINK_AUDIT, GOLINK_AUDIT_PATH | No | Disable audit or override path | | GOLINK_APPROVAL_DIR, GOLINK_SCHEDULE_DIR | No | Override approval / schedule store dirs | | GOLINK_REFRESH_LOCK_PATH | No | Override sidecar flock path for auto-refresh single-flight serialisation (default: $XDG_STATE_HOME/golink/session.refresh.lock) | | GOLINK_RECORD, GOLINK_REPLAY | No | Record/replay HTTP exchanges to a JSONL cassette |

Tokens are stored in the OS keyring — never on disk or in logs.

> Auto-refresh hard-fail. When a session is within 5 minutes of access-token > expiry, golink runs auth refresh inline before dispatching the user's > command. The refresh is serialised across processes via a sidecar flock > (GOLINK_REFRESH_LOCK_PATH, default $XDG_STATE_HOME/golink/session.refresh.lock). > If the lock cannot be acquired, or if persisting the refreshed session fails, > the command exits 4 with an auth error — prior versions warned and proceeded. > Because LinkedIn rotates refresh tokens, silent half-state is worse than an > upfront failure: re-run the command (or run golink auth refresh explicitly) > after resolving the underlying issue.

> Redirect URI gotcha. LinkedIn matches OAuth redirects on the exact > registered URL including the port. Register http://127.0.0.1:/callback > for whatever port GOLINK_REDIRECT_PORT resolves to; 0 only works if the > app is configured with every port you might fall through to (rare). Native > PKCE needs no client_secret; standard OAuth 2.0 requires > GOLINK_AUTH_FLOW=oauth2 and GOLINK_CLIENT_SECRET.

> Native PKCE scope gotcha. Some LinkedIn apps reject OpenID scopes > (openid profile email) on the native PKCE endpoint. For those apps, set > GOLINK_AUTH_SCOPES="w_member_social r_profile_basicinfo". golink will > try GET /v2/me?fields=id as a non-OIDC profile fallback. If that endpoint > is unavailable for your app, also provide > GOLINK_MEMBER_URN=urn:li:person: so golink can still build post author > URNs.

> Standard OAuth 2.0 fallback. If your LinkedIn app shows > Not enough permissions to access Native PKCE protocol, use the confidential > OAuth flow instead: > > ``sh > export GOLINK_AUTH_FLOW=oauth2 > export GOLINK_CLIENT_SECRET= > export GOLINK_AUTH_SCOPES="w_member_social r_profile_basicinfo" > export GOLINK_REDIRECT_PORT=8080 > ` > > Add http://127.0.0.1:8080/callback to the app's Authorized redirect URLs. > LinkedIn requires an exact match: same scheme (http), host (127.0.0.1), > port (8080), and path (/callback). localhost and 127.0.0.1` are not > interchangeable for this check.

Idempotency keys

Mutating commands that dispatch a single operation (post create, post delete, post edit, post reshare, post schedule, comment add, react add, and execute) accept --idempotency-key . On the first successful call the result is cached locally; subsequent calls with the same key within 24 hours replay the cached result and set from_cache: true in the envelope — the transport is never called again.

golink --json post create --text "Hello" --idempotency-key my-post-1
# Second call returns from_cache:true, no network request
golink --json post create --text "Hello" --idempotency-key my-post-1

Store location (first match wins):

  1. GOLINK_IDEMPOTENCY_PATH=/custom/path.jsonl
  2. $XDG_STATE_HOME/golink/idempotency.jsonl
  3. ~/.local/state/golink/idempotency.jsonl

The store is an append-only JSONL file created with mode 0600 in a 0700 directory. Keys expire after 24 hours. Using a key with a different command than it was first recorded against returns a validation_error.

Batch operations

golink batch reads a JSONL file where each line is an operation:

{"command":"post create","args":{"text":"Hello batch","visibility":"PUBLIC"},"idempotency_key":"b-1"}
{"command":"post schedule","args":{"at":"2026-05-06T09:00:00Z","text":"Queued post","visibility":"PUBLIC"}}
{"command":"post delete","args":{"post_urn":"urn:li:share:123"}}
{"command":"comment add","args":{"post_urn":"urn:li:share:456","text":"nice"},"dry_run":true}
{"command":"react add","args":{"post_urn":"urn:li:share:789","type":"LIKE"},"idempotency_key":"r-1"}

Results stream to stdout as JSONL — one BatchOpResultOutput envelope per input line.

Supported commands: post create, post schedule, post delete, comment add, react add.

# Basic run
golink --json batch ops.jsonl

# Stop on first error
golink --json batch --fail-fast ops.jsonl

# Exit 2 if any op is non-ok (CI gate)
golink --json batch --strict ops.jsonl

# Up to 4 parallel workers
golink --json batch --concurrency 4 ops.jsonl

# Skip ops already recorded in ops.jsonl.progress
golink --json batch --resume ops.jsonl

# Read from stdin
cat ops.jsonl | golink --json batch -

Flags:

| Flag | Default | Description | |---|---|---| | --fail-fast | false | Stop on the first op error (exit 5) | | --continue-on-error | true | Continue after op errors | | --concurrency | 1 | Parallel workers (max 4) | | --strict | false | Exit 2 if any op is non-ok | | `-

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.