Skip to content

Commit 8c2c221

Browse files
committed
Merge the web UI batch follow-ups: connectors menu, Tools settings page, MCP server management
2 parents 4496c10 + 01c501c commit 8c2c221

35 files changed

Lines changed: 1544 additions & 188 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Clean up the web composer capability control: the selected tools no longer render as chips in the toolbar, the button reads "Connectors", and the menu panel stays inside the window when its content loads.

.changeset/web-mcp-server-crud.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": minor
3+
---
4+
5+
Add, edit, and remove your own MCP servers from the connectors page in the web UI; a new or edited server starts with your next session.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": minor
3+
---
4+
5+
Move the web tool picker out of the composer menu into a Tools page in settings, where the full list fits. Every tool stays on until you turn one off, and the selection still applies to the current session only.

apps/pythinker-web/src/App.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,10 @@ const {
624624
},
625625
onLoadConnectors: () => { void client.loadConnectors(); },
626626
onLoadPlugins: () => { void client.loadPlugins(); },
627+
onLoadTools: () => {
628+
const sessionId = client.activeSessionId.value;
629+
if (sessionId) void client.loadCapabilityData(sessionId);
630+
},
627631
onLoadSubagents: () => { void client.loadSubagents(); },
628632
});
629633
@@ -693,6 +697,14 @@ async function openModelPicker(): Promise<void> {
693697
}
694698
}
695699
700+
/** Narrow the active session's tool selection from the settings Tools page. */
701+
function applySessionTools(names: string[]): void {
702+
// `updateCapabilities` rolls its optimistic write back and reports the
703+
// failure itself, then rethrows; swallowing here keeps a failed write from
704+
// surfacing as an unhandled rejection.
705+
void client.updateCapabilities({ tools: names }).catch(() => undefined);
706+
}
707+
696708
async function openProviders(): Promise<void> {
697709
providersLoading.value = true;
698710
providersUnavailable.value = false;
@@ -1100,11 +1112,20 @@ function openPr(url: string): void {
11001112
:skills="client.skills.value"
11011113
:connectors="client.connectors.value"
11021114
:connectors-loading="client.connectorsLoading.value"
1115+
:connectors-error="client.connectorsError.value"
11031116
:sessions="client.sessionsWithUsage.value"
11041117
:plugins="client.plugins.value"
11051118
:subagents="client.subagents.value"
1119+
:tools="client.toolsBySession.value[client.activeSessionId.value]"
1120+
:tools-loading="client.toolsLoadingBySession.value[client.activeSessionId.value] === true"
1121+
:enabled-tools="client.activeSessionCapabilities.value.tools"
1122+
:session-id="client.activeSessionId.value"
11061123
@set-plugin-enabled="client.setPluginEnabled($event.pluginId, $event.enabled)"
1124+
@set-tools="applySessionTools($event)"
11071125
@restart-connector="client.restartConnector($event)"
1126+
@create-connector="client.createConnector($event)"
1127+
@update-connector="client.updateConnector($event.connectorId, $event.input)"
1128+
@remove-connector="client.removeConnector($event)"
11081129
@set-theme="client.setTheme($event)"
11091130
@set-color-scheme="client.setColorScheme($event)"
11101131
@set-ui-font-size="client.setUiFontSize($event)"

apps/pythinker-web/src/api/daemon/client.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import type { PythinkerApiConfig } from '../config';
55
import { buildRestUrl, buildWsUrl } from '../config';
66
import type {
77
AppConfig,
8+
AppMcpServerDefinition,
9+
AppMcpServerInput,
810
AppMessage,
911
AppMessageRole,
1012
AppModel,
@@ -151,6 +153,17 @@ interface WireMcpServer {
151153
status: 'connected' | 'connecting' | 'disconnected' | 'error';
152154
tool_count: number;
153155
last_error?: string;
156+
editable: boolean;
157+
definition?: WireMcpServerDefinition;
158+
}
159+
160+
interface WireMcpServerDefinition {
161+
transport: 'stdio' | 'http' | 'sse';
162+
command?: string;
163+
args?: string[];
164+
env?: Record<string, string>;
165+
url?: string;
166+
headers?: Record<string, string>;
154167
}
155168

156169
interface WirePlugin {
@@ -175,6 +188,28 @@ interface WireAgentProfile {
175188
when_to_use?: string;
176189
}
177190

191+
function toAppMcpDefinition(definition: WireMcpServerDefinition): AppMcpServerDefinition {
192+
return {
193+
transport: definition.transport,
194+
command: definition.command,
195+
args: definition.args,
196+
env: definition.env,
197+
url: definition.url,
198+
headers: definition.headers,
199+
};
200+
}
201+
202+
function toWireMcpDefinition(input: AppMcpServerInput): WireMcpServerDefinition {
203+
return {
204+
transport: input.transport,
205+
command: input.command,
206+
args: input.args,
207+
env: input.env,
208+
url: input.url,
209+
headers: input.headers,
210+
};
211+
}
212+
178213
interface WireArchiveResult {
179214
archived: true;
180215
}
@@ -791,9 +826,34 @@ export class DaemonPythinkerWebApi implements PythinkerWebApi {
791826
status: server.status,
792827
toolCount: server.tool_count,
793828
lastError: server.last_error,
829+
editable: server.editable,
830+
definition: server.definition === undefined ? undefined : toAppMcpDefinition(server.definition),
794831
}));
795832
}
796833

834+
async createConnector(input: AppMcpServerInput): Promise<{ created: true }> {
835+
return this.http.post<{ created: true }>('/mcp/servers', {
836+
mcp_server_id: input.name,
837+
config: toWireMcpDefinition(input),
838+
});
839+
}
840+
841+
async updateConnector(
842+
connectorId: string,
843+
input: AppMcpServerInput,
844+
): Promise<{ updated: true }> {
845+
return this.http.put<{ updated: true }>(
846+
`/mcp/servers/${encodeURIComponent(connectorId)}`,
847+
{ config: toWireMcpDefinition(input) },
848+
);
849+
}
850+
851+
async removeConnector(connectorId: string): Promise<{ deleted: true }> {
852+
return this.http.delete<{ deleted: true }>(
853+
`/mcp/servers/${encodeURIComponent(connectorId)}`,
854+
);
855+
}
856+
797857
async listPlugins(): Promise<AppPlugin[]> {
798858
const data = await this.http.get<{ plugins: WirePlugin[] }>('/plugins');
799859
return (data.plugins ?? []).map((plugin) => ({

apps/pythinker-web/src/api/daemon/http.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ export class DaemonHttpClient {
170170
return this.request<T>('PATCH', path, body);
171171
}
172172

173+
async put<T>(path: string, body: unknown): Promise<T> {
174+
return this.request<T>('PUT', path, body);
175+
}
176+
173177
async delete<T>(path: string): Promise<T> {
174178
return this.request<T>('DELETE', path);
175179
}

apps/pythinker-web/src/api/types.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,13 +662,28 @@ export interface AppSubagent {
662662
}
663663

664664
/** A configured MCP server ("connector") and its live connection state. */
665+
export interface AppMcpServerDefinition {
666+
transport: 'stdio' | 'http' | 'sse';
667+
command?: string;
668+
args?: string[];
669+
env?: Record<string, string>;
670+
url?: string;
671+
headers?: Record<string, string>;
672+
}
673+
674+
export interface AppMcpServerInput extends AppMcpServerDefinition {
675+
name: string;
676+
}
677+
665678
export interface AppConnector {
666679
id: string;
667680
name: string;
668681
transport: 'stdio' | 'http' | 'sse';
669682
status: 'connected' | 'connecting' | 'disconnected' | 'error';
670683
toolCount: number;
671684
lastError?: string;
685+
editable: boolean;
686+
definition?: AppMcpServerDefinition;
672687
}
673688

674689
/** One tool available to the current session. */
@@ -720,6 +735,12 @@ export interface PythinkerWebApi {
720735
activateSkill(sessionId: string, skillName: string, args?: string): Promise<{ activated: true; skillName: string }>;
721736
/** Configured MCP servers — GET /mcp/servers. */
722737
listConnectors(): Promise<AppConnector[]>;
738+
/** Create one user-global MCP server — POST /mcp/servers. */
739+
createConnector(input: AppMcpServerInput): Promise<{ created: true }>;
740+
/** Replace one user-global MCP server — PUT /mcp/servers/{id}. */
741+
updateConnector(connectorId: string, input: AppMcpServerInput): Promise<{ updated: true }>;
742+
/** Remove one user-global MCP server — DELETE /mcp/servers/{id}. */
743+
removeConnector(connectorId: string): Promise<{ deleted: true }>;
723744
/** Restart one MCP server — POST /mcp/servers/{id}:restart. */
724745
restartConnector(connectorId: string): Promise<{ restarting: true }>;
725746
/** Installed plugins — GET /plugins. */

0 commit comments

Comments
 (0)