From 21f4a1241bb9849319a61a9c939031ab40115e5d Mon Sep 17 00:00:00 2001 From: Renzo Beux Date: Fri, 28 Aug 2026 13:32:19 -0300 Subject: [PATCH] feat(plugins): mcp:tools permission and the mcpToolProvider hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modelled on the hook:* family rather than on geolocation:read. The host dispatches INTO the child for every tool call, so it needs what HOOK_PERMISSION buys: providersOf()'s active-and-holds-the-grant check, the second gate in invokeHook, and the acting-user binding. That last one is the point. The user is bound host-side from the supervisor's invocation map, so a tool called by user A reaches A's data whatever the model puts in args. The permission keeps no hook: prefix because the surface it opens is MCP, not a TREK render slot, and nothing keys off that prefix. It is the first HOOK_PERMISSION value shaped that way; `satisfies` still holds. One commit, because it cannot be split. requireTotalCoverage makes a HOOK_PERMISSION key with no @PluginHook consumer a boot failure, not a test failure, and PluginRpcRegistryService.onModuleInit runs in every buildApp() — so a half-landed version breaks every integration test in the repo. The generator is chained to it too: it refuses to run without both the en admin label and the CLI hint. 15s is the longest budget of any read hook. The others back a render someone is waiting on and fall back when they time out; this one backs a chat turn, and the plugin is likely calling a third-party API of its own. Still under the supervisor's unused 30s default, which is a hung client rather than a budget. One callTool fn for every tool, since tool names are runtime data and hookContracts() is keyed by (hook, fn). PluginDefinition exists in two copies with no generator between them, so both get McpToolProvider. grantGaps and the dev mock host need no change: both read HOOK_PERMISSION, which is generated. PLUGHOOK-010, plus the CONTRACTS row. --- plugin-sdk/src/cli/ui.ts | 1 + plugin-sdk/src/generated/host-facts.ts | 2 ++ plugin-sdk/src/index.ts | 20 +++++++++++++ .../src/nest/plugins/plugin-hooks.service.ts | 28 +++++++++++++++++++ server/src/nest/plugins/protocol/envelope.ts | 15 ++++++++++ server/src/nest/plugins/runtime/plugin-sdk.ts | 20 +++++++++++++ .../tests/unit/plugins/plugin-hooks.test.ts | 11 ++++++++ shared/src/i18n/ar/admin.ts | 2 ++ shared/src/i18n/br/admin.ts | 2 ++ shared/src/i18n/ca/admin.ts | 2 ++ shared/src/i18n/cs/admin.ts | 2 ++ shared/src/i18n/de/admin.ts | 2 ++ shared/src/i18n/en/admin.ts | 2 ++ shared/src/i18n/es/admin.ts | 2 ++ shared/src/i18n/fr/admin.ts | 2 ++ shared/src/i18n/gr/admin.ts | 2 ++ shared/src/i18n/hu/admin.ts | 2 ++ shared/src/i18n/id/admin.ts | 2 ++ shared/src/i18n/it/admin.ts | 2 ++ shared/src/i18n/ja/admin.ts | 2 ++ shared/src/i18n/ko/admin.ts | 2 ++ shared/src/i18n/nl/admin.ts | 2 ++ shared/src/i18n/pl/admin.ts | 2 ++ shared/src/i18n/ru/admin.ts | 2 ++ shared/src/i18n/sv/admin.ts | 2 ++ shared/src/i18n/tr/admin.ts | 2 ++ shared/src/i18n/uk/admin.ts | 2 ++ shared/src/i18n/vi/admin.ts | 2 ++ shared/src/i18n/zh-TW/admin.ts | 2 ++ shared/src/i18n/zh/admin.ts | 2 ++ shared/src/plugin-permissions.ts | 2 ++ 31 files changed, 145 insertions(+) diff --git a/plugin-sdk/src/cli/ui.ts b/plugin-sdk/src/cli/ui.ts index 08400859a..61fbcabc6 100644 --- a/plugin-sdk/src/cli/ui.ts +++ b/plugin-sdk/src/cli/ui.ts @@ -257,6 +257,7 @@ export const PERMISSION_FAMILIES: PermissionFamily[] = [ { value: 'ai:invoke', hint: 'Call the instance\'s configured AI model' }, { value: 'oauth:client', hint: 'Obtain OAuth access tokens for the user' }, { value: 'geolocation:read', hint: 'Ask the host for the browser\'s live position in your frames (browser prompt still applies)' }, + { value: 'mcp:tools', hint: 'Publish MCP tools an assistant can call, running as the requesting user' }, ], }, ]; diff --git a/plugin-sdk/src/generated/host-facts.ts b/plugin-sdk/src/generated/host-facts.ts index b9be32dec..6cd9656bd 100644 --- a/plugin-sdk/src/generated/host-facts.ts +++ b/plugin-sdk/src/generated/host-facts.ts @@ -18,6 +18,7 @@ export const HOOK_PERMISSION: Readonly> = { journalEntryProvider: 'hook:journal-entry-provider', tripCardProvider: 'hook:trip-card-provider', notificationChannel: 'hook:notification-channel', + mcpToolProvider: 'mcp:tools', }; export const KNOWN_PERMISSIONS: string[] = [ @@ -84,6 +85,7 @@ export const KNOWN_PERMISSIONS: string[] = [ 'ai:invoke', 'oauth:client', 'geolocation:read', + 'mcp:tools', ]; export const METHOD_PERMISSION: Readonly> = { diff --git a/plugin-sdk/src/index.ts b/plugin-sdk/src/index.ts index 34af7f6e9..2f6b848ae 100644 --- a/plugin-sdk/src/index.ts +++ b/plugin-sdk/src/index.ts @@ -773,6 +773,25 @@ export interface PluginSubscription { handler(payload: unknown, ctx: PluginContext): Promise | void; } +/** + * Publishes MCP tools on TREK's own MCP server, so an assistant can call into + * the plugin as the requesting user. Requires the `mcp:tools` permission. + * + * `tools` lists which of the tools declared in `capabilities.mcpTools` this + * build actually implements. The host advertises the intersection of the two: + * the manifest is signed and re-consented, this list is not, so a tool the + * manifest never declared is ignored rather than trusted. + * + * `callTool` receives the plugin-local name, without the `plugin__` prefix + * the tool is advertised under. Return anything JSON-serialisable; the host + * wraps it into an MCP result envelope, and a throw becomes a tool error the + * assistant can read and recover from. + */ +export interface McpToolProvider { + tools: string[]; + callTool(call: { name: string; args: unknown }, ctx: PluginContext): Promise | unknown; +} + export interface PluginDefinition { onLoad?(ctx: PluginContext): Promise | void; onUnload?(ctx: PluginContext): Promise | void; @@ -815,6 +834,7 @@ export interface PluginDefinition { journalEntryProvider?: JournalEntryProvider; tripCardProvider?: TripCardProvider; notificationChannel?: NotificationChannel; + mcpToolProvider?: McpToolProvider; }; /** Functions exposed to dependents (names must match manifest `capabilities.provides`). */ exports?: Record; diff --git a/server/src/nest/plugins/plugin-hooks.service.ts b/server/src/nest/plugins/plugin-hooks.service.ts index 1daf276f9..376785421 100644 --- a/server/src/nest/plugins/plugin-hooks.service.ts +++ b/server/src/nest/plugins/plugin-hooks.service.ts @@ -10,6 +10,14 @@ export interface HookChannelMessage { tripName?: string; } +/** One MCP tool call, as the plugin that published the tool receives it. */ +export interface HookMcpToolCall { + /** The plugin-local tool name, without the `plugin__` advertisement prefix. */ + name: string; + /** Whatever the assistant passed. Validated against the declared schema first. */ + args: unknown; +} + /** The waypoint request a route provider is asked to solve. */ export interface HookRouteRequest { tripId: number; @@ -146,4 +154,24 @@ export class PluginHooks { testNotification(pluginId: string, userSettings: unknown): Promise { return this.runtime.invokeHook(pluginId, 'notificationChannel', 'test', [userSettings], undefined, 8000); } + + /** + * One MCP tool call the assistant made, dispatched into the plugin that + * published the tool. + * + * The longest budget of any read hook. Every other one backs a render that a + * user is waiting on, and falls back to something reasonable when it times + * out; this one backs a chat turn, and the plugin is likely talking to a + * third-party API of its own. Still well under the supervisor's 30s default, + * which no hook uses: 30s of a blocked MCP request is not a budget, it is a + * hung client. + * + * One fn for every tool, because tool names are runtime data and + * hookContracts() is keyed by (hook, fn). The name is checked against the + * advertised set before we get here. + */ + @PluginHook('mcpToolProvider', { permission: 'mcp:tools', fn: 'callTool', timeoutMs: 15_000 }) + callMcpTool(pluginId: string, call: HookMcpToolCall, userId: number): Promise { + return this.runtime.invokeHook(pluginId, 'mcpToolProvider', 'callTool', [call], userId, 15_000); + } } diff --git a/server/src/nest/plugins/protocol/envelope.ts b/server/src/nest/plugins/protocol/envelope.ts index 65aecb9d1..c4822376a 100644 --- a/server/src/nest/plugins/protocol/envelope.ts +++ b/server/src/nest/plugins/protocol/envelope.ts @@ -388,6 +388,17 @@ export const KNOWN_PERMISSIONS = [ // applies. Nothing is sent to the server; the plugin's server code never sees a // position unless its own client ships it through one of its routes. 'geolocation:read', + // Lets the plugin publish tools on TREK's own MCP server, so an assistant can + // call into it as the requesting user. Modelled on the hook:* family rather + // than on geolocation:read above: the host dispatches INTO the child for + // every call, so it needs the active-and-holds-the-grant check and the + // acting-user binding that HOOK_PERMISSION gives it. The name has no hook: + // prefix because the surface it opens is MCP, not a TREK render slot, and + // nothing keys off that prefix. + // + // This is the real boundary on plugin tools. The user-facing plugins:use + // OAuth scope only decides whether they are advertised to a client at all. + 'mcp:tools', ] as const; /** @@ -431,6 +442,10 @@ export const HOOK_PERMISSION = { journalEntryProvider: 'hook:journal-entry-provider', tripCardProvider: 'hook:trip-card-provider', notificationChannel: 'hook:notification-channel', + // The one entry whose permission is not hook:*-shaped; see mcp:tools in + // KNOWN_PERMISSIONS. Being here is what buys it providersOf()'s grant check + // and requireTotalCoverage's boot assertion. + mcpToolProvider: 'mcp:tools', } as const satisfies Record; export type HookKey = keyof typeof HOOK_PERMISSION; diff --git a/server/src/nest/plugins/runtime/plugin-sdk.ts b/server/src/nest/plugins/runtime/plugin-sdk.ts index 2df10cb03..6e2525586 100644 --- a/server/src/nest/plugins/runtime/plugin-sdk.ts +++ b/server/src/nest/plugins/runtime/plugin-sdk.ts @@ -667,6 +667,25 @@ export interface PluginSubscription { handler(payload: unknown, ctx: PluginContext): Promise | void; } +/** + * Publishes MCP tools on TREK's own MCP server, so an assistant can call into + * the plugin as the requesting user. Requires the `mcp:tools` permission. + * + * `tools` lists which of the tools declared in `capabilities.mcpTools` this + * build actually implements. The host advertises the intersection of the two: + * the manifest is signed and re-consented, this list is not, so a tool the + * manifest never declared is ignored rather than trusted. + * + * `callTool` receives the plugin-local name, without the `plugin__` prefix + * the tool is advertised under. Return anything JSON-serialisable; the host + * wraps it into an MCP result envelope, and a throw becomes a tool error the + * assistant can read and recover from. + */ +export interface McpToolProvider { + tools: string[]; + callTool(call: { name: string; args: unknown }, ctx: PluginContext): Promise | unknown; +} + export interface PluginDefinition { onLoad?(ctx: PluginContext): Promise | void; onUnload?(ctx: PluginContext): Promise | void; @@ -709,6 +728,7 @@ export interface PluginDefinition { journalEntryProvider?: JournalEntryProvider; tripCardProvider?: TripCardProvider; notificationChannel?: NotificationChannel; + mcpToolProvider?: McpToolProvider; }; /** Functions exposed to dependents (names must match manifest capabilities.provides). */ exports?: Record; diff --git a/server/tests/unit/plugins/plugin-hooks.test.ts b/server/tests/unit/plugins/plugin-hooks.test.ts index 6255c0f6c..ddf53f3af 100644 --- a/server/tests/unit/plugins/plugin-hooks.test.ts +++ b/server/tests/unit/plugins/plugin-hooks.test.ts @@ -45,6 +45,7 @@ const CONTRACTS: Array<[hook: string, fn: string, timeoutMs: number]> = [ ['tripCardProvider', 'getCards', 5000], ['notificationChannel', 'send', 8000], ['notificationChannel', 'test', 8000], + ['mcpToolProvider', 'callTool', 15_000], ]; describe('PluginHooks contracts', () => { @@ -137,6 +138,16 @@ describe('PluginHooks contracts', () => { expect(invokeHook).toHaveBeenCalledWith('p', 'notificationChannel', 'test', [{ token: 'x' }], undefined, 8000); }); + it('PLUGHOOK-010 an MCP tool call binds the requesting user and takes the long budget', async () => { + // The acting user is the whole reason this goes through a hook rather than a + // bespoke invoke: it is bound host-side from the supervisor's invocation map, + // so a tool called by user A reaches A's data whatever the model put in args. + const { hooks: h, invokeHook } = hooks(); + const call = { name: 'echo', args: { value: 'x' } }; + await h.callMcpTool('p', call, 7); + expect(invokeHook).toHaveBeenCalledWith('p', 'mcpToolProvider', 'callTool', [call], 7, 15_000); + }); + it('PLUGHOOK-009 the class is listed in its module providers', () => { // A decorated class no module registers is invisible to discovery, which would // now fail boot on "no host-side consumer" rather than fail quietly. Cheapest diff --git a/shared/src/i18n/ar/admin.ts b/shared/src/i18n/ar/admin.ts index 4b622c200..07beed613 100644 --- a/shared/src/i18n/ar/admin.ts +++ b/shared/src/i18n/ar/admin.ts @@ -338,6 +338,8 @@ const admin: TranslationStrings = { 'admin.plugins.perm.hook:day-schedule-provider': 'إلحاق مدخلات زمنية بخطة اليوم (توقفات للشحن، هوامش احتياطية)', 'admin.plugins.perm.hook:day-tint-provider': 'تلوين الأيام في خطة اليوم (مثلاً تحديد المرحلة التي ينتمي إليها كل يوم)', + 'admin.plugins.perm.mcp:tools': + 'نشر أدوات يمكن لمساعد ذكاء اصطناعي تشغيلها نيابة عنك (يعمل بالصلاحيات التي تمنحها للإضافة هنا، وليس بصلاحياته هو)', 'admin.plugins.perm.geolocation:read': 'الاستعلام عن موقعك المباشر ما دامت إحدى واجهاته مفتوحة (يقرأه TREK بإذن الموقع الممنوح لهذا الموقع الإلكتروني، لا بإذن خاص بالإضافة)', 'admin.plugins.perm.hook:pdf-section-provider': 'إضافة أقسام نصية إلى تصدير PDF للرحلة', diff --git a/shared/src/i18n/br/admin.ts b/shared/src/i18n/br/admin.ts index 0e89ff2fd..80dfe2dbe 100644 --- a/shared/src/i18n/br/admin.ts +++ b/shared/src/i18n/br/admin.ts @@ -324,6 +324,8 @@ const admin: TranslationStrings = { 'Anexar entradas de horário ao plano do dia (paradas de recarga, margens de segurança)', 'admin.plugins.perm.hook:day-tint-provider': 'Colorir os dias no plano do dia (por exemplo, a qual trecho da viagem um dia pertence)', + 'admin.plugins.perm.mcp:tools': + 'Publicar ferramentas que um assistente de IA pode executar em seu nome (ele age com o acesso que você concede ao plugin aqui, não com o dele)', 'admin.plugins.perm.geolocation:read': 'Consultar sua localização em tempo real enquanto uma das visualizações dele estiver aberta (o TREK a lê com a permissão de localização deste site, não com uma permissão própria do plugin)', 'admin.plugins.perm.hook:pdf-section-provider': 'Anexar seções de texto à exportação em PDF da viagem', diff --git a/shared/src/i18n/ca/admin.ts b/shared/src/i18n/ca/admin.ts index 5d5c13a51..14be364ff 100644 --- a/shared/src/i18n/ca/admin.ts +++ b/shared/src/i18n/ca/admin.ts @@ -471,6 +471,8 @@ const admin: TranslationStrings = { 'Afegir entrades horàries al pla del dia (parades de recàrrega, marges de seguretat)', 'admin.plugins.perm.hook:day-tint-provider': 'Acolorir els dies del pla del dia (per exemple, a quin tram del viatge pertany un dia)', + 'admin.plugins.perm.mcp:tools': + "Publicar eines que un assistent d'IA pot executar en nom teu (actua amb els permisos que concedeixes aquí al connector, no amb els seus)", 'admin.plugins.perm.geolocation:read': "Consultar la teva ubicació en temps real mentre una de les seves vistes és oberta (el TREK la llegeix amb el permís d'ubicació d'aquest lloc, no amb un permís propi del connector)", 'admin.plugins.updateConsentTitle': 'Aquesta actualització necessita permisos nous', diff --git a/shared/src/i18n/cs/admin.ts b/shared/src/i18n/cs/admin.ts index 7d0f8e0b9..b660ae7ed 100644 --- a/shared/src/i18n/cs/admin.ts +++ b/shared/src/i18n/cs/admin.ts @@ -325,6 +325,8 @@ const admin: TranslationStrings = { 'Připojovat časové položky k dennímu plánu (zastávky na nabíjení, časové rezervy)', 'admin.plugins.perm.hook:day-tint-provider': 'Barevně odlišovat dny v denním plánu (například ke které části cesty den patří)', + 'admin.plugins.perm.mcp:tools': + 'Zveřejňovat nástroje, které může AI asistent spustit vaším jménem (jedná s oprávněními, která zde pluginu udělíte, ne se svými)', 'admin.plugins.perm.geolocation:read': 'Zjišťovat vaši aktuální polohu, dokud je otevřené některé z jeho zobrazení (TREK ji čte pod oprávněním k poloze pro tento web, ne pod vlastním oprávněním doplňku)', 'admin.plugins.perm.hook:pdf-section-provider': 'Připojovat textové sekce k PDF exportu cesty', diff --git a/shared/src/i18n/de/admin.ts b/shared/src/i18n/de/admin.ts index c384dba7f..a1b0d9ded 100644 --- a/shared/src/i18n/de/admin.ts +++ b/shared/src/i18n/de/admin.ts @@ -330,6 +330,8 @@ const admin: TranslationStrings = { 'admin.plugins.perm.hook:day-schedule-provider': 'Zeiteinträge an den Tagesplan anhängen (Ladestopps, Puffer)', 'admin.plugins.perm.hook:day-tint-provider': 'Tage im Tagesplan farblich markieren (z. B. zu welchem Reiseabschnitt ein Tag gehört)', + 'admin.plugins.perm.mcp:tools': + 'Tools veröffentlichen, die ein KI-Assistent in deinem Namen ausführen kann (es handelt mit den Rechten, die du dem Plugin hier erteilst, nicht mit denen des Assistenten)', 'admin.plugins.perm.geolocation:read': 'Deinen Live-Standort abfragen, solange eine seiner Ansichten offen ist (TREK liest ihn mit der Standortfreigabe dieser Seite, nicht mit einer eigenen des Plugins)', 'admin.plugins.perm.hook:pdf-section-provider': 'Textabschnitte an den PDF-Export einer Reise anhängen', diff --git a/shared/src/i18n/en/admin.ts b/shared/src/i18n/en/admin.ts index 4af119bbb..3bab693cb 100644 --- a/shared/src/i18n/en/admin.ts +++ b/shared/src/i18n/en/admin.ts @@ -371,6 +371,8 @@ const admin: TranslationStrings = { 'Attach time entries to the day plan (charging stops, security buffers)', 'admin.plugins.perm.hook:day-tint-provider': 'Colour-code days in the day plan (e.g. which leg of the trip a day belongs to)', + 'admin.plugins.perm.mcp:tools': + 'Publish tools that an AI assistant can run on your behalf (it acts with the access you grant the plugin here, not with the assistant’s own)', 'admin.plugins.perm.geolocation:read': "Ask for your live position while one of its views is open (TREK reads it under this site's location permission, not the plugin's own)", 'admin.plugins.perm.hook:pdf-section-provider': 'Append text sections to the trip PDF export', diff --git a/shared/src/i18n/es/admin.ts b/shared/src/i18n/es/admin.ts index 5cedc9ee4..0f1396a5f 100644 --- a/shared/src/i18n/es/admin.ts +++ b/shared/src/i18n/es/admin.ts @@ -333,6 +333,8 @@ const admin: TranslationStrings = { 'Añadir entradas de tiempo al plan del día (paradas de carga, márgenes de seguridad)', 'admin.plugins.perm.hook:day-tint-provider': 'Colorear los días del plan del día (por ejemplo, a qué tramo del viaje pertenece un día)', + 'admin.plugins.perm.mcp:tools': + 'Publicar herramientas que un asistente de IA puede ejecutar en tu nombre (actúa con el acceso que concedes aquí al complemento, no con el del asistente)', 'admin.plugins.perm.geolocation:read': 'Consultar tu ubicación en tiempo real mientras una de sus vistas esté abierta (TREK la lee con el permiso de ubicación de este sitio, no con uno propio del plugin)', 'admin.plugins.perm.hook:pdf-section-provider': 'Añadir secciones de texto a la exportación PDF del viaje', diff --git a/shared/src/i18n/fr/admin.ts b/shared/src/i18n/fr/admin.ts index b31d87485..231a6866a 100644 --- a/shared/src/i18n/fr/admin.ts +++ b/shared/src/i18n/fr/admin.ts @@ -335,6 +335,8 @@ const admin: TranslationStrings = { 'Ajouter des entrées horaires au plan de la journée (arrêts de recharge, marges de sécurité)', 'admin.plugins.perm.hook:day-tint-provider': 'Colorer les jours du plan de la journée (par exemple, à quelle étape du voyage appartient un jour)', + 'admin.plugins.perm.mcp:tools': + "Publier des outils qu'un assistant IA peut exécuter en votre nom (il agit avec les accès que vous accordez ici à l'extension, pas avec les siens)", 'admin.plugins.perm.geolocation:read': "Demander votre position en temps réel tant que l'une de ses vues est ouverte (TREK la lit avec l'autorisation de localisation de ce site, pas avec une autorisation propre à l'extension)", 'admin.plugins.perm.hook:pdf-section-provider': "Ajouter des sections de texte à l'export PDF du voyage", diff --git a/shared/src/i18n/gr/admin.ts b/shared/src/i18n/gr/admin.ts index 53994cd7d..988523ee9 100644 --- a/shared/src/i18n/gr/admin.ts +++ b/shared/src/i18n/gr/admin.ts @@ -383,6 +383,8 @@ const admin: TranslationStrings = { 'Προσθήκη χρονικών καταχωρίσεων στο πλάνο της ημέρας (στάσεις φόρτισης, περιθώρια ασφαλείας)', 'admin.plugins.perm.hook:day-tint-provider': 'Χρωματική σήμανση ημερών στο πλάνο της ημέρας (π.χ. σε ποιο σκέλος του ταξιδιού ανήκει μια ημέρα)', + 'admin.plugins.perm.mcp:tools': + 'Δημοσίευση εργαλείων που μπορεί να εκτελέσει ένας βοηθός AI εκ μέρους σας (ενεργεί με τα δικαιώματα που δίνετε εδώ στο πρόσθετο, όχι με τα δικά του)', 'admin.plugins.perm.geolocation:read': 'Αίτημα της τρέχουσας τοποθεσίας σας όσο μία από τις προβολές του είναι ανοιχτή (το TREK τη διαβάζει με την άδεια τοποθεσίας αυτού του ιστότοπου, όχι με δική του άδεια του πρόσθετου)', 'admin.plugins.perm.hook:pdf-section-provider': 'Προσθήκη ενοτήτων κειμένου στην εξαγωγή PDF του ταξιδιού', diff --git a/shared/src/i18n/hu/admin.ts b/shared/src/i18n/hu/admin.ts index 42f8b906c..ae90e61c9 100644 --- a/shared/src/i18n/hu/admin.ts +++ b/shared/src/i18n/hu/admin.ts @@ -336,6 +336,8 @@ const admin: TranslationStrings = { 'Időbejegyzések hozzáadása a napi tervhez (töltőmegállók, biztonsági ráhagyások)', 'admin.plugins.perm.hook:day-tint-provider': 'A napok színezése a napi tervben (például hogy az út melyik szakaszához tartozik egy nap)', + 'admin.plugins.perm.mcp:tools': + 'Olyan eszközök közzététele, amelyeket egy MI-asszisztens futtathat a nevedben (a bővítménynek itt adott jogosultságokkal működik, nem a sajátjával)', 'admin.plugins.perm.geolocation:read': 'Az élő tartózkodási helyed lekérdezése, amíg valamelyik nézete nyitva van (a TREK az oldal helymeghatározási engedélyével olvassa ki, nem a bővítmény saját engedélyével)', 'admin.plugins.perm.hook:pdf-section-provider': 'Szöveges szakaszok hozzáfűzése az utazás PDF-exportjához', diff --git a/shared/src/i18n/id/admin.ts b/shared/src/i18n/id/admin.ts index 01692f10a..7e8214e46 100644 --- a/shared/src/i18n/id/admin.ts +++ b/shared/src/i18n/id/admin.ts @@ -380,6 +380,8 @@ const admin: TranslationStrings = { 'Melampirkan entri waktu ke rencana hari (pemberhentian pengisian daya, jeda keamanan)', 'admin.plugins.perm.hook:day-tint-provider': 'Memberi warna pada hari di rencana hari (misalnya bagian perjalanan mana yang memuat suatu hari)', + 'admin.plugins.perm.mcp:tools': + 'Menerbitkan alat yang dapat dijalankan asisten AI atas nama Anda (bertindak dengan akses yang Anda berikan ke plugin di sini, bukan milik asisten)', 'admin.plugins.perm.geolocation:read': 'Meminta lokasi terkinimu selama salah satu tampilannya terbuka (TREK membacanya dengan izin lokasi situs ini, bukan izin milik plugin sendiri)', 'admin.plugins.perm.hook:pdf-section-provider': 'Menambahkan bagian teks ke ekspor PDF perjalanan', diff --git a/shared/src/i18n/it/admin.ts b/shared/src/i18n/it/admin.ts index a0272b6e8..ccf60c8c8 100644 --- a/shared/src/i18n/it/admin.ts +++ b/shared/src/i18n/it/admin.ts @@ -332,6 +332,8 @@ const admin: TranslationStrings = { 'Aggiungere voci orarie al piano della giornata (soste di ricarica, margini di sicurezza)', 'admin.plugins.perm.hook:day-tint-provider': 'Colorare i giorni nel piano della giornata (ad esempio a quale tratta del viaggio appartiene un giorno)', + 'admin.plugins.perm.mcp:tools': + 'Pubblicare strumenti che un assistente IA può eseguire per tuo conto (agisce con i permessi che concedi qui al plugin, non con i propri)', 'admin.plugins.perm.geolocation:read': 'Richiedere la tua posizione in tempo reale mentre una delle sue viste è aperta (TREK la legge con il permesso di posizione di questo sito, non con uno proprio del plugin)', 'admin.plugins.perm.hook:pdf-section-provider': "Aggiungere sezioni di testo all'esportazione PDF del viaggio", diff --git a/shared/src/i18n/ja/admin.ts b/shared/src/i18n/ja/admin.ts index ca97b1940..42ca230d0 100644 --- a/shared/src/i18n/ja/admin.ts +++ b/shared/src/i18n/ja/admin.ts @@ -347,6 +347,8 @@ const admin: TranslationStrings = { 'admin.plugins.perm.hook:day-schedule-provider': '日程プランに時間エントリーを追加(充電ストップ、保安検査のバッファ)', 'admin.plugins.perm.hook:day-tint-provider': '日程プランの日付を色分け(例:その日が旅程のどの区間に属するか)', + 'admin.plugins.perm.mcp:tools': + 'AI アシスタントがあなたの代わりに実行できるツールを公開する(ここでプラグインに付与した権限で動作し、アシスタント自身の権限ではありません)', 'admin.plugins.perm.geolocation:read': 'このプラグインのビューが開いている間、あなたの現在地を取得(TREK がこのサイトの位置情報の許可を使って読み取ります。プラグイン専用の許可は求められません)', 'admin.plugins.perm.hook:pdf-section-provider': '旅行の PDF エクスポートにテキストセクションを追加', diff --git a/shared/src/i18n/ko/admin.ts b/shared/src/i18n/ko/admin.ts index 85c764b3a..4ff17b6a3 100644 --- a/shared/src/i18n/ko/admin.ts +++ b/shared/src/i18n/ko/admin.ts @@ -348,6 +348,8 @@ const admin: TranslationStrings = { '플래너가 일자 경로 계산에 사용할 라우팅 프로필 제공(예: 충전소 경유 전기차 라우팅)', 'admin.plugins.perm.hook:day-schedule-provider': '일자 계획에 시간 항목 추가(충전 정차, 보안 검색 여유 시간)', 'admin.plugins.perm.hook:day-tint-provider': '일자 계획에서 날짜를 색으로 구분(예: 해당 날짜가 여행의 어느 구간인지)', + 'admin.plugins.perm.mcp:tools': + 'AI 어시스턴트가 사용자를 대신해 실행할 수 있는 도구 게시 (여기서 플러그인에 부여한 권한으로 동작하며, 어시스턴트 자신의 권한이 아닙니다)', 'admin.plugins.perm.geolocation:read': '해당 플러그인 화면이 열려 있는 동안 실시간 위치 요청 (TREK이 이 사이트에 부여된 위치 권한으로 읽으며, 플러그인용 권한을 따로 묻지 않음)', 'admin.plugins.perm.hook:pdf-section-provider': '여행 PDF 내보내기에 텍스트 섹션 추가', diff --git a/shared/src/i18n/nl/admin.ts b/shared/src/i18n/nl/admin.ts index 00669739e..a551eb204 100644 --- a/shared/src/i18n/nl/admin.ts +++ b/shared/src/i18n/nl/admin.ts @@ -331,6 +331,8 @@ const admin: TranslationStrings = { 'admin.plugins.perm.hook:day-schedule-provider': 'Tijdsitems aan het dagplan koppelen (laadstops, buffertijden)', 'admin.plugins.perm.hook:day-tint-provider': 'Dagen in het dagplan een kleur geven (bijvoorbeeld bij welk deel van de reis een dag hoort)', + 'admin.plugins.perm.mcp:tools': + 'Tools publiceren die een AI-assistent namens jou kan uitvoeren (die handelt met de rechten die je de plug-in hier geeft, niet met die van de assistent)', 'admin.plugins.perm.geolocation:read': 'Je live locatie opvragen zolang een van zijn weergaven open is (TREK leest die met de locatietoestemming van deze site, niet met een eigen toestemming van de plug-in)', 'admin.plugins.perm.hook:pdf-section-provider': 'Tekstsecties toevoegen aan de PDF-export van de reis', diff --git a/shared/src/i18n/pl/admin.ts b/shared/src/i18n/pl/admin.ts index 49126c983..6dc3dbdb4 100644 --- a/shared/src/i18n/pl/admin.ts +++ b/shared/src/i18n/pl/admin.ts @@ -327,6 +327,8 @@ const admin: TranslationStrings = { 'Dołączanie wpisów czasowych do planu dnia (postoje na ładowanie, bufory czasowe)', 'admin.plugins.perm.hook:day-tint-provider': 'Oznaczanie dni w planie dnia kolorami (na przykład do którego etapu podróży należy dany dzień)', + 'admin.plugins.perm.mcp:tools': + 'Publikowanie narzędzi, które asystent AI może uruchomić w Twoim imieniu (działa z uprawnieniami nadanymi tutaj wtyczce, nie z własnymi)', 'admin.plugins.perm.geolocation:read': 'Odczytywanie Twojej aktualnej lokalizacji, gdy otwarty jest któryś z jego widoków (TREK odczytuje ją z uprawnieniem do lokalizacji tej witryny, a nie z własnym uprawnieniem wtyczki)', 'admin.plugins.perm.hook:pdf-section-provider': 'Dołączanie sekcji tekstowych do eksportu PDF podróży', diff --git a/shared/src/i18n/ru/admin.ts b/shared/src/i18n/ru/admin.ts index 1fd66e9a1..31ec6881f 100644 --- a/shared/src/i18n/ru/admin.ts +++ b/shared/src/i18n/ru/admin.ts @@ -332,6 +332,8 @@ const admin: TranslationStrings = { 'Добавлять записи времени в план дня (остановки для зарядки, запас времени на контроль)', 'admin.plugins.perm.hook:day-tint-provider': 'Отмечать дни в плане цветом (например, к какому этапу поездки относится день)', + 'admin.plugins.perm.mcp:tools': + 'Публиковать инструменты, которые ИИ-ассистент может запускать от вашего имени (он действует с правами, выданными плагину здесь, а не со своими)', 'admin.plugins.perm.geolocation:read': 'Запрашивать ваше местоположение в реальном времени, пока открыто одно из его представлений (TREK читает его с разрешением на геолокацию для этого сайта, а не с отдельным разрешением плагина)', 'admin.plugins.perm.hook:pdf-section-provider': 'Добавлять текстовые разделы в PDF-экспорт поездки', diff --git a/shared/src/i18n/sv/admin.ts b/shared/src/i18n/sv/admin.ts index 89185d771..f47a1e94f 100644 --- a/shared/src/i18n/sv/admin.ts +++ b/shared/src/i18n/sv/admin.ts @@ -389,6 +389,8 @@ const admin: TranslationStrings = { 'Lägga till tidsposter i dagsplanen (laddstopp, säkerhetsmarginaler)', 'admin.plugins.perm.hook:day-tint-provider': 'Färgkoda dagar i dagsplanen (till exempel vilken etapp av resan en dag tillhör)', + 'admin.plugins.perm.mcp:tools': + 'Publicera verktyg som en AI-assistent kan köra åt dig (det agerar med de rättigheter du ger tillägget här, inte med assistentens egna)', 'admin.plugins.perm.geolocation:read': 'Fråga efter din position i realtid medan en av dess vyer är öppen (TREK läser den med den här webbplatsens platsbehörighet, inte med en egen behörighet för tillägget)', 'admin.plugins.perm.hook:pdf-section-provider': 'Lägga till textavsnitt i resans PDF-export', diff --git a/shared/src/i18n/tr/admin.ts b/shared/src/i18n/tr/admin.ts index 842816a45..c7a17b43b 100644 --- a/shared/src/i18n/tr/admin.ts +++ b/shared/src/i18n/tr/admin.ts @@ -376,6 +376,8 @@ const admin: TranslationStrings = { 'Gün planına zaman girişleri ekler (şarj durakları, güvenlik tamponları)', 'admin.plugins.perm.hook:day-tint-provider': 'Gün planındaki günleri renklendirir (örneğin bir günün yolculuğun hangi etabına ait olduğu)', + 'admin.plugins.perm.mcp:tools': + 'Bir yapay zekâ asistanının sizin adınıza çalıştırabileceği araçlar yayımlamak (burada eklentiye verdiğiniz yetkilerle çalışır, asistanın kendi yetkileriyle değil)', 'admin.plugins.perm.geolocation:read': 'Görünümlerinden biri açıkken canlı konumunu sorar (TREK konumu bu sitenin konum izniyle okur, eklenti için ayrıca izin sorulmaz)', 'admin.plugins.perm.hook:pdf-section-provider': 'Seyahat PDF dışa aktarımına metin bölümleri ekler', diff --git a/shared/src/i18n/uk/admin.ts b/shared/src/i18n/uk/admin.ts index 615692ea9..09f8529d3 100644 --- a/shared/src/i18n/uk/admin.ts +++ b/shared/src/i18n/uk/admin.ts @@ -329,6 +329,8 @@ const admin: TranslationStrings = { 'Додавати часові записи до плану дня (зупинки для заряджання, буфери часу)', 'admin.plugins.perm.hook:day-tint-provider': 'Позначати дні в плані дня кольором (наприклад, до якого етапу подорожі належить день)', + 'admin.plugins.perm.mcp:tools': + 'Публікувати інструменти, які ШІ-асистент може запускати від вашого імені (він діє з правами, наданими плаґіну тут, а не зі своїми)', 'admin.plugins.perm.geolocation:read': 'Запитувати ваше місцезнаходження в реальному часі, поки відкрито одне з його подань (TREK зчитує його з дозволом на геолокацію для цього сайту, а не з окремим дозволом розширення)', 'admin.plugins.perm.hook:pdf-section-provider': 'Додавати текстові розділи до PDF-експорту подорожі', diff --git a/shared/src/i18n/vi/admin.ts b/shared/src/i18n/vi/admin.ts index 227c4a53c..fb3bedee8 100644 --- a/shared/src/i18n/vi/admin.ts +++ b/shared/src/i18n/vi/admin.ts @@ -332,6 +332,8 @@ const admin: TranslationStrings = { 'Gắn các mục thời gian vào kế hoạch ngày (điểm dừng sạc, thời gian đệm an ninh)', 'admin.plugins.perm.hook:day-tint-provider': 'Tô màu các ngày trong kế hoạch ngày (ví dụ ngày đó thuộc chặng nào của chuyến đi)', + 'admin.plugins.perm.mcp:tools': + 'Xuất bản các công cụ mà trợ lý AI có thể chạy thay bạn (nó hoạt động với quyền bạn cấp cho tiện ích tại đây, không phải quyền của trợ lý)', 'admin.plugins.perm.geolocation:read': 'Hỏi vị trí trực tiếp của bạn khi một trong các chế độ xem của nó đang mở (TREK đọc vị trí bằng quyền vị trí của trang này, không phải quyền riêng của plugin)', 'admin.plugins.perm.hook:pdf-section-provider': 'Thêm các phần văn bản vào bản xuất PDF của chuyến đi', diff --git a/shared/src/i18n/zh-TW/admin.ts b/shared/src/i18n/zh-TW/admin.ts index 989c16efc..c3e6ebf7e 100644 --- a/shared/src/i18n/zh-TW/admin.ts +++ b/shared/src/i18n/zh-TW/admin.ts @@ -335,6 +335,8 @@ const admin: TranslationStrings = { '提供路線規劃設定檔,供規劃工具計算每日路線(例如含充電停靠站的電動車路線規劃)', 'admin.plugins.perm.hook:day-schedule-provider': '向每日行程附加時間項目(充電停靠站、緩衝時間)', 'admin.plugins.perm.hook:day-tint-provider': '為每日行程中的日期標示顏色(例如某天屬於旅程的哪一段)', + 'admin.plugins.perm.mcp:tools': + '發布 AI 助理可以代你執行的工具(它以你在此處授予外掛的權限運作,而非助理自身的權限)', 'admin.plugins.perm.geolocation:read': '在其任一檢視開啟時查詢你的即時位置(由 TREK 使用本站台既有的位置權限讀取,不會另外為外掛詢問)', 'admin.plugins.perm.hook:pdf-section-provider': '向行程 PDF 匯出附加文字段落', 'admin.plugins.perm.hook:atlas-layer-provider': '在 Atlas 世界地圖上標示國家(例如願望清單或旅遊警示)', diff --git a/shared/src/i18n/zh/admin.ts b/shared/src/i18n/zh/admin.ts index b6ba03fb1..1de53f286 100644 --- a/shared/src/i18n/zh/admin.ts +++ b/shared/src/i18n/zh/admin.ts @@ -293,6 +293,8 @@ const admin: TranslationStrings = { 'admin.plugins.perm.hook:route-provider': '提供路线规划方案,供规划器计算每日路线(例如带充电停靠点的电动车路线)', 'admin.plugins.perm.hook:day-schedule-provider': '向当日计划添加时间条目(充电停靠点、安全缓冲时间)', 'admin.plugins.perm.hook:day-tint-provider': '为当日计划中的日期标注颜色(例如某天属于行程的哪一段)', + 'admin.plugins.perm.mcp:tools': + '发布 AI 助手可以代你运行的工具(它以你在此处授予插件的权限运行,而非助手自身的权限)', 'admin.plugins.perm.geolocation:read': '在其某个视图打开时请求你的实时位置(由 TREK 使用本站点已有的位置权限读取,不会单独为插件询问)', 'admin.plugins.perm.hook:pdf-section-provider': '向行程 PDF 导出追加文本段落', 'admin.plugins.perm.hook:atlas-layer-provider': '在 Atlas 世界地图上高亮国家(例如愿望清单或旅行提醒)', diff --git a/shared/src/plugin-permissions.ts b/shared/src/plugin-permissions.ts index 752daca74..04c1dc5a8 100644 --- a/shared/src/plugin-permissions.ts +++ b/shared/src/plugin-permissions.ts @@ -67,6 +67,7 @@ export const PLUGIN_PERMISSIONS: readonly string[] = [ 'ai:invoke', 'oauth:client', 'geolocation:read', + 'mcp:tools', ]; export const PLUGIN_HOOK_PERMISSION: Readonly> = { @@ -85,4 +86,5 @@ export const PLUGIN_HOOK_PERMISSION: Readonly> = { journalEntryProvider: 'hook:journal-entry-provider', tripCardProvider: 'hook:trip-card-provider', notificationChannel: 'hook:notification-channel', + mcpToolProvider: 'mcp:tools', };