Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,38 @@ billing-write, and file workflows.

## MCP

Sume MCP is coming soon and is not part of this public CLI launch release yet.
Use direct CLI commands today for auth, schema discovery, Avatar, Avatar Video,
jobs, balance, and usage workflows.
Hosted remote Sume MCP is available at:

```text
https://mcp.sume.com/mcp
```

Paste that URL into a hosted remote MCP client that supports OAuth. The client
starts the Sume OAuth flow, the user signs in with Sume, and the client stores
an OAuth token for read-only MCP tools. This hosted flow is separate from local
`sume login`; it does not require users to create or paste Sume API keys.

Show client setup guidance from the CLI:

```bash
sume mcp remote --json
sume mcp remote --agent cursor --json
```

OAuth Phase 1 is read-only. Write tools and paid generation tools remain blocked
for hosted OAuth tokens. Read-only OAuth asset helpers include `assets.list`,
`assets.get`, and `assets.download_url`. The remote MCP server also exposes
write-gated `assets.upload_url` and `assets.complete` for compatible non-OAuth
sessions. `assets.upload_file` remains local-only because a hosted MCP server
cannot read the caller's local filesystem.

Local stdio MCP is coming soon and is not part of this public CLI launch release
yet. Use direct CLI commands today for auth, schema discovery, Avatar, Avatar
Video, jobs, balance, and usage workflows.

The open-source repository may contain MCP implementation code while the
capability is being prepared. Public MCP setup and server startup are not
launched yet, and the CLI does not write MCP client config in this release.
capability is being prepared. Local MCP setup and server startup are not
launched yet, and the CLI does not write local MCP client config in this release.

## Website

Expand Down
37 changes: 37 additions & 0 deletions src/commands/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "../lib/mcp-launch-status.js";
import { supportedMcpClientAgents } from "../lib/mcp-client-config.js";
import { renderResult } from "../lib/render.js";
import { buildRemoteMcpSetup } from "../lib/remote-mcp-setup.js";

type McpInstallOptions = {
agent?: string;
Expand All @@ -16,6 +17,10 @@ type McpDoctorOptions = {
agent?: string;
};

type McpRemoteOptions = {
agent?: string;
};

export function registerMcpCommand(program: Command) {
const mcp = program
.command("mcp", { hidden: true })
Expand Down Expand Up @@ -59,8 +64,40 @@ export function registerMcpCommand(program: Command) {
["Launched", payload.launched],
"",
payload.message,
"",
["Hosted remote MCP", payload.remote.status],
["Remote endpoint", payload.remote.endpoint],
["Remote OAuth", payload.remote.oauth],
"Use direct Sume CLI commands today.",
],
});
});

mcp
.command("remote")
.description("Show hosted remote MCP OAuth setup guidance.")
.option(
"--agent <agent>",
`Target MCP client. Supported: ${supportedMcpClientAgents().join(", ")}.`,
)
.action((options: McpRemoteOptions, command: Command) => {
const payload = buildRemoteMcpSetup(options.agent);
renderResult(payload, {
json: getMode(command).json,
human: [
"Sume hosted remote MCP",
"",
["Endpoint", payload.endpoint],
["OAuth resource", payload.oauth.resource],
["Scopes", payload.oauth.scopes.join(", ")],
["Local CLI login required", payload.oauth.local_cli_login_required],
["Manual API key required", payload.oauth.manual_api_key_required],
["OAuth write tools", payload.phase.write_tools_enabled_for_oauth],
["OAuth paid tools", payload.phase.paid_tools_enabled_for_oauth],
"",
"Paste the endpoint into a hosted remote MCP client and complete its OAuth flow.",
"Local `sume login` remains for the CLI and does not configure hosted MCP OAuth.",
],
});
});
}
18 changes: 18 additions & 0 deletions src/lib/mcp-launch-status.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { CliError } from "./errors.js";
import {
REMOTE_MCP_ENDPOINT,
REMOTE_MCP_OAUTH_RESOURCE,
REMOTE_MCP_SCOPE,
} from "./remote-mcp-setup.js";

export const MCP_COMING_SOON_MESSAGE =
"Sume MCP is coming soon and is not launched in this CLI release yet.";
Expand All @@ -20,6 +25,19 @@ export function mcpComingSoonStatus() {
launched: false,
message: MCP_COMING_SOON_MESSAGE,
recommended_surface: "direct_cli",
remote: {
status: "available",
endpoint: REMOTE_MCP_ENDPOINT,
oauth: "hosted_client_flow",
resource: REMOTE_MCP_OAUTH_RESOURCE,
scopes: [REMOTE_MCP_SCOPE],
local_cli_login_required: false,
},
local_stdio: {
status: "coming_soon",
launched: false,
command: "sume mcp",
},
next_steps: MCP_COMING_SOON_NEXT_STEPS,
};
}
Expand Down
148 changes: 148 additions & 0 deletions src/lib/remote-mcp-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { CliError } from "./errors.js";
import {
type McpClientAgent,
supportedMcpClientAgents,
} from "./mcp-client-config.js";

export const REMOTE_MCP_ENDPOINT = "https://mcp.sume.com/mcp";
export const REMOTE_MCP_OAUTH_RESOURCE = REMOTE_MCP_ENDPOINT;
export const REMOTE_MCP_SCOPE = "mcp:read";

const REMOTE_MCP_OAUTH_ASSET_HELPERS = [
"assets.list",
"assets.get",
"assets.download_url",
] as const;

const REMOTE_MCP_WRITE_GATED_ASSET_HELPERS = [
"assets.list",
"assets.get",
"assets.upload_url",
"assets.complete",
"assets.download_url",
] as const;

const LOCAL_ONLY_MCP_ASSET_HELPERS = ["assets.upload_file"] as const;

type RemoteMcpClientSetup = {
agent: McpClientAgent;
client: string;
setup: string[];
};

const REMOTE_MCP_CLIENTS: RemoteMcpClientSetup[] = [
{
agent: "codex",
client: "Codex",
setup: [
"Add a streamable HTTP MCP server named `sume` with url `https://mcp.sume.com/mcp`.",
"Run the client's MCP OAuth login flow for the `sume` server.",
],
},
{
agent: "claude-code",
client: "Claude Code",
setup: [
"Add a remote HTTP MCP server named `sume` with url `https://mcp.sume.com/mcp`.",
"Run the client's MCP OAuth login flow for the `sume` server.",
],
},
{
agent: "cursor",
client: "Cursor",
setup: [
"Add a remote MCP server named `sume` with url `https://mcp.sume.com/mcp`.",
"Use Cursor's OAuth flow when it prompts you to connect Sume.",
],
},
];

export type RemoteMcpSetup = {
object: "remote_mcp_setup";
schema_version: 1;
endpoint: typeof REMOTE_MCP_ENDPOINT;
oauth: {
type: "oauth";
resource: typeof REMOTE_MCP_OAUTH_RESOURCE;
scopes: [typeof REMOTE_MCP_SCOPE];
manual_api_key_required: false;
local_cli_login_required: false;
};
local_stdio_mcp: {
launched: false;
command: "sume mcp";
status: "coming_soon";
};
phase: {
read_only_tools_enabled: true;
write_tools_enabled_for_oauth: false;
paid_tools_enabled_for_oauth: false;
};
asset_helpers: {
oauth_read_only_helpers: typeof REMOTE_MCP_OAUTH_ASSET_HELPERS;
remote_write_gated_helpers: typeof REMOTE_MCP_WRITE_GATED_ASSET_HELPERS;
local_only_helpers: typeof LOCAL_ONLY_MCP_ASSET_HELPERS;
};
clients: RemoteMcpClientSetup[];
notes: string[];
};

export function buildRemoteMcpSetup(agent?: string): RemoteMcpSetup {
const clients = agent
? [findRemoteMcpClient(agent)]
: REMOTE_MCP_CLIENTS.map((client) => ({ ...client, setup: [...client.setup] }));

return {
object: "remote_mcp_setup",
schema_version: 1,
endpoint: REMOTE_MCP_ENDPOINT,
oauth: {
type: "oauth",
resource: REMOTE_MCP_OAUTH_RESOURCE,
scopes: [REMOTE_MCP_SCOPE],
manual_api_key_required: false,
local_cli_login_required: false,
},
local_stdio_mcp: {
launched: false,
command: "sume mcp",
status: "coming_soon",
},
phase: {
read_only_tools_enabled: true,
write_tools_enabled_for_oauth: false,
paid_tools_enabled_for_oauth: false,
},
asset_helpers: {
oauth_read_only_helpers: REMOTE_MCP_OAUTH_ASSET_HELPERS,
remote_write_gated_helpers: REMOTE_MCP_WRITE_GATED_ASSET_HELPERS,
local_only_helpers: LOCAL_ONLY_MCP_ASSET_HELPERS,
},
clients,
notes: [
"Hosted remote MCP OAuth is separate from local `sume login`.",
"Paste the endpoint into clients that support hosted remote MCP OAuth.",
"OAuth Phase 1 is read-only; write and paid tools stay blocked for OAuth tokens.",
"`assets.upload_file` remains local-only because a hosted MCP server cannot read local files.",
],
};
}

function findRemoteMcpClient(agent: string) {
if (!supportedMcpClientAgents().includes(agent as McpClientAgent)) {
throw new CliError(`Unsupported MCP agent: ${agent}`, {
code: "invalid_argument",
hint: `Use one of: ${supportedMcpClientAgents().join(", ")}`,
});
}

const client = REMOTE_MCP_CLIENTS.find((entry) => entry.agent === agent);
if (!client) {
throw new CliError(`Unsupported MCP agent: ${agent}`, {
code: "invalid_argument",
hint: `Use one of: ${supportedMcpClientAgents().join(", ")}`,
});
}

return { ...client, setup: [...client.setup] };
}
70 changes: 70 additions & 0 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,77 @@ describe("CLI", () => {
status: "coming_soon",
launched: false,
recommended_surface: "direct_cli",
remote: {
status: "available",
endpoint: "https://mcp.sume.com/mcp",
oauth: "hosted_client_flow",
scopes: ["mcp:read"],
local_cli_login_required: false,
},
local_stdio: {
status: "coming_soon",
launched: false,
command: "sume mcp",
},
});
expect(requests).toHaveLength(0);
},
);
});

it("reports hosted remote MCP OAuth setup without calling the API", async () => {
await withMockApi(
() => ({ data: { ok: true } }),
async (baseUrl, requests) => {
const { stdout } = await runCli(
["--json", "mcp", "remote", "--agent", "cursor"],
baseUrl,
);
const parsed = JSON.parse(stdout);
expect(parsed).toMatchObject({
object: "remote_mcp_setup",
endpoint: "https://mcp.sume.com/mcp",
oauth: {
type: "oauth",
resource: "https://mcp.sume.com/mcp",
scopes: ["mcp:read"],
manual_api_key_required: false,
local_cli_login_required: false,
},
local_stdio_mcp: {
launched: false,
command: "sume mcp",
status: "coming_soon",
},
phase: {
read_only_tools_enabled: true,
write_tools_enabled_for_oauth: false,
paid_tools_enabled_for_oauth: false,
},
asset_helpers: {
oauth_read_only_helpers: [
"assets.list",
"assets.get",
"assets.download_url",
],
remote_write_gated_helpers: [
"assets.list",
"assets.get",
"assets.upload_url",
"assets.complete",
"assets.download_url",
],
local_only_helpers: ["assets.upload_file"],
},
clients: [
expect.objectContaining({
agent: "cursor",
client: "Cursor",
}),
],
});
expect(JSON.stringify(parsed)).not.toMatch(/sume_(?:live|test)_/u);
expect(JSON.stringify(parsed)).not.toMatch(/bearer\s+[A-Za-z0-9._-]+/iu);
expect(requests).toHaveLength(0);
},
);
Expand Down
Loading
Loading