Developers

MCP is the API. Connect Claude, ChatGPT or your coding agent, and build on plain files you can read.

For you

Add Ghostlyn to Claude, ChatGPT or your coding agent.

Client setup

For your coding agent

Paste this into Claude Code, Codex or OpenCode.

Then restart your agent and sign in.

Architecture

A small core runs the agent. Everything a team sees lives in bundles on top of it. Click a part to read what it does.

GhostlynMCP serverReads, writesRuns codeLoads skillsTool codeTable schemasSyncMCP client

Open standards

What you write here works in your other agents, and theirs works here.

AGENTS.md

agents.md

A project's standing instructions, in the file Codex and Cursor already read. A folder's own AGENTS.md adds rules where it applies.

Read more

Agent Skills

agentskills.io

Each skill is a folder with a SKILL.md, scripts and references, so a skill moves between Claude Code and Ghostlyn unchanged.

Read more

Ghostlyn is a server that Claude, ChatGPT and coding agents connect to, and a client of any MCP connector you add.

Read more

A connected server's tools can ship their own screens, shown in the chat in a sandboxed frame.

Read more

Agent Plugins

agent-plugins.org

A bundle keeps skills in a plugin's skills/ layout, so an imported plugin brings its skills over. Add its MCP servers as connectors.

Read more

Data and models

Where your data sits, which models see it and what leaves with a connected client.

Stored
Hosted in Germany, in the EU
Inference
Anthropic, OpenAI and others, with an EU-only route
Ownership
Bundles export as files, sync to your Git

What a connected client reads enters its own model under its provider's terms.

Privacy and access

MCP server

Two endpoints, OAuth sign-in with no key, and a scope for every tool.

/mcpEverything the signed-in member can reach
/mcp/bundles/:slugOne project, pinned to one connector

OAuth 2.1 with dynamic client registration. The member approves each scope, and revokes the client from Settings, MCP.

ScopeGrants
records:readSearch past chats and records
recall
tables:readQuery table rows
query
tables:writeAdd, change and remove table rows
save
remove
volumes:readRead files in projects you can open
read
glob
grep
lookup
volumes:writeWrite, move and delete those files
write
edit
delete
move
web:fetchFetch pages and search the web as Ghostlyn
fetch
curl
search
skills:listList skills you can run
find
skills:invokeRun a skill
agent:invokeHand a task to the Ghostlyn agent
invoke

Rate limits

Per member and per address. Ask support to raise them for your team.

Tool calls per member60 a minute, 600 an hour
Sign-in requests per address120 a minute, 1,200 an hour
Client registrations per address60 an hour

Each connected client, what it may do and Revoke sit in your settings.Open MCP settings

Your tools

Bring what you already run into Ghostlyn. Each links to the feature it belongs to.

Any MCP serverConnectors
Start a task from a webhookTasks
Sync a project to GitHubProjects
The agent in Slack channelsSlack

Webhooks take a 64 KB body and start up to 25 runs per webhook a day.

Small core

The core runs the loop, the sandbox and storage. Connectors, skills and apps are bundles you can read and change.

Read

Every built-in bundle opens in the app, and the agent reads it too.

Copy

Take a copy of any bundle into your organization, built-ins included.

Change

Edit the copy yourself or ask the agent to. It runs the moment it saves.

28 built-in bundles

5 in the core, 23 extended
Chart
Diagram
Productivity
System
Word
Customer success
Design
Engineering
Finance
Ghostwriting
Gmail
Google Calendar
+16
Browse them in Ghostlyn

Improve a built-in

File a bug or a fix for any built-in bundle on GitHub. Your coding agent can do it for you.

gh issue create -R ghostlyn/ghostlyn
A bundle's files in the app, the same ones the agent reads.

Bundles

A bundle is plain files: a manifest, tools in TypeScript and skills in the Agent Skills format.

solidtime/

import { jsonClient } from "ghostlyn/http";
import {
  dateRangeUtc,
  rollup,
  type SolidtimeProject,
  type SolidtimeClient,
} from "./summary.ts";

export const instructions = `solidtime tracks time entries for an organization, by person, client and
project. Call summary with a start and end date, inclusive, and an IANA
timezone, to get totals for that range. Name organization only when the
member belongs to more than one solidtime organization.`;

const client = jsonClient({ credential: "solidtime" });

type Membership = {
  id: string;
  role: string;
  organization: { id: string; name: string };
};

type PageMeta = { last_page?: number };

async function pageAll<T>(path: string): Promise<T[]> {
  const results: T[] = [];
  let page = 1;
  for (;;) {
    const sep = path.includes("?") ? "&" : "?";
    const res = await client("GET", `${path}${sep}page=${page}`);
    const data = (res.data as T[] | undefined) ?? [];
    results.push(...data);
    const meta = res.meta as PageMeta | undefined;
    const lastPage = meta?.last_page ?? 1;
    if (page >= lastPage) break;
    page++;
  }
  return results;
}

function pickMembership(
  memberships: Membership[],
  organization: string | undefined
): Membership {
  if (memberships.length === 0) {
    throw new Error("No solidtime organization membership found.");
  }
  const names = memberships.map(m => m.organization.name).join(", ");
  if (organization) {
    const match = memberships.find(
      m =>
        m.organization.name === organization ||
        m.organization.id === organization
    );
    if (!match) {
      throw new Error(
        `No solidtime organization named "${organization}". You belong to: ${names}.`
      );
    }
    return match;
  }
  if (memberships.length === 1) return memberships[0];
  throw new Error(
    `You belong to more than one solidtime organization. Name one: ${names}.`
  );
}

/**
 * Summarizes tracked solidtime hours by person, client within each person and
 * project within each client, plus team totals per client, for an inclusive
 * date range.
 *
 * @messageTemplate Summarizing solidtime hours
 * @readOnly
 */
export async function summary({
  start,
  end,
  timezone,
  organization,
}: {
  /** Inclusive start date, YYYY-MM-DD. */
  start: string;
  /** Inclusive end date, YYYY-MM-DD. */
  end: string;
  /** IANA timezone the range is local to, such as Europe/Helsinki. */
  timezone: string;
  /** Organization name or id, needed only with more than one membership. */
  organization?: string;
}) {
  const memberships = await client("GET", "/api/v1/users/me/memberships");
  const membership = pickMembership(
    (memberships.data as Membership[] | undefined) ?? [],
    organization
  );
  const orgId = membership.organization.id;
  const restrictedToSelf = membership.role === "employee";

  const range = dateRangeUtc(start, end, timezone);
  const params = new URLSearchParams({
    group: "user",
    sub_group: "project",
    start: range.start,
    end: range.end,
  });
  if (restrictedToSelf) params.set("member_id", membership.id);

  const [aggregate, projects, clients] = await Promise.all([
    client(
      "GET",
      `/api/v1/organizations/${orgId}/time-entries/aggregate?${params}`
    ),
    pageAll<SolidtimeProject>(`/api/v1/organizations/${orgId}/projects`),
    pageAll<SolidtimeClient>(`/api/v1/organizations/${orgId}/clients`),
  ]);

  const result = rollup(
    aggregate.data as Parameters<typeof rollup>[0],
    projects,
    clients
  );

  return {
    organization: membership.organization.name,
    start,
    end,
    restrictedToSelf,
    ...result,
  };
}

Read from bundles/extended/solidtime when the page builds, so the example always runs.

Sandbox

Tools and scripts run isolated, with a fixed set of modules. Click one for what it does.

Runtime
QuickJS compiled to WebAssembly
Each run
30 seconds, 1 MB of output, 50 nested tool calls
Network
Only through ghostlyn/http, with host checks
Credentials
Named, injected only toward their own host
Not available
Filesystem, sockets, child processes

Deno standard library

Node built-ins

npm packages

Ghostlyn

Docs

The references Ghostlyn's own support agent reads, published as they are.

Updates

What shipped, and whether Ghostlyn is running right now.